07-Java基础-多线程 & 单例
多线程、单例
-
线程
线程是程序执行的一条路径, 一个进程中可以包含多条线程。
多线程并发执行可以提高程序的效率, 可以同时完成多项工作。 -
并行和并发
并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
并行:两个或多个事件在同一时刻点发生。
并发:两个或多个事件在同一时间段内发生。 -
创建线程
线程类:Thread类和Thread的子类才能称之为线程。
方式一:继承Thread类。
方式二:实现Runnable接口。
方式三:匿名内部类。(用的最少)
1、方式一
public class ThreadDemo {
public static void main (String[] args) {
// 方式一:继承Thread类
// 4)在main方法中创建线程对象,并启动线程。
MyThread myThread = new MyThread();
// 5)开启线程
myThread.start();
myThread.getName(); //获取当前线程
}
}
// 1)定义一个类A继承于java.long. Thread类。
class MyThread extends Thread{
// 2)在A类中覆盖Thread的run方法。
@Override
public void run() {
super.run();
// 3)在run方法中编写需要执行的代码。
for (int i = 0; i < 100; i ++){
Thread.currentThread().setName("线程---2"); // 设置线程名称
System.out.println(Thread.currentThread()); // 获取当前线程
}
}
}
好处:可以直接使用Thread类中的方法,代码简单。
弊端:如果已经有了父类,就不能用这种方法。
2、方式二
public class ThreadDemo {
public static void main (String[] args) {
// 方式二:实现Runnable接口
// 4)在main方法中创建线程对象,并启动线程。
Thread thread = new Thread(new GameThread());
thread.start();
}
}
// 1)定义一个类A实现java.lang.Runnable接口。
class GameThread implements Runnable{
// 2)在A类中实现Runnable接口中的run方法。
@Override
public void run() {
// 3)在run方法中编写需要执行的代码。
for (int i = 0; i < 100; i ++){
this.setName("线程---1"); // 设置线程名称
System.out.println(Thread.currentThread()); // 获取当前线程
}
}
}
好处:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的。
弊端:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法。
3、匿名内部类创建线程
public class ThreadDemo {
public static void main (String[] args) {
//方式三:匿名内部类
// 1)将Runnable的子类对象传递给Thread的构造方法
new Thread(new Runnable() {
// 2)重写run方法
@Override
public void run() {
// 3)将要执行的代码写在run方法中
for (int i = 0; i < 100; i ++){
Thread.currentThread().setName("线程---3"); // 设置线程名称
System.out.println(Thread.currentThread()); //获取当前线程
}
}
}).start(); // 开启线程
}
}
-
线程休眠
让执行的线程暂停一段时间,进入计时等待状态。
方法:state void sleep(long mills)
调用sleep后,当前线程放弃CPU,sleep线程不会获得执行机会,此状态下的线程不会释放同步锁。
该方法更多的用于网络延迟。
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 20; i >= 0; i--){
System.out.println("倒计时" + i + "秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
-
守护线程
在后台运行的线程,其目的是为其它线程提供服务。JVM的垃圾回收线程就是典型的后台线程。
setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出。
特点:若所有的前台线程都死亡,后台线程自动死亡,前台线程没有结束,后台线程是不会结束的。
thread.isDaemon() 返回是否为后台线程。
前台线程创建的线程默认是前台线程,可以通过setDaemon()方法设置为后台线程(setDaemon()方法必须在start方法前调用)。后台线程创建的线程默认为后台线程。
private static void daemonDemo(){
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread());
}
}
}).start();
Thread thread1 = new Thread(){
@Override
public void run() {
super.run();
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread());
}
}
};
Thread thread = new Thread(){
@Override
public void run() {
super.run();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread());
}
}
};
thread.setDaemon(true);
thread.start();
}
-
加入线程
join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定线程执行指定毫秒后继续
private static void joinThreadDemo(){
final Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread() + "-----111111");
}
}
};
thread.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if (i == 2){
try {
// thread.join(); // 线程1开始执行,执行完后线程2才开始执行
thread.join(1); // 线程1执行1毫米,然后线程1和线程2交替执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread() + "-----222222");
}
}
}).start();
}
-
礼让线程(了解)
Thread.yield():表示当前线程对象提示调度器自己愿意让出CPU资源,但是调度器可以自由忽略该意愿。
调用该方法后,线程进入就绪状态,所以某个线程调用了yield方法之后,调度器又把它调度出来重新执行。
开发中很少使用该方法,主要用于调试,它可能有助于因多线程调度的竞争条件下的错误重现。 -
设置线程优先级(了解)
每个线程都有优先级,优先级的高低只和线程获得执行机会的多少有关,与执行先后顺序无关,执行顺序取决于CPU的调度。
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
// 设置优先级(建议三个常量就可以了)
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
Thread.currentThread().setPriority(3);
// 获取当前线程优先级
Thread.currentThread().getPriority()
每个线程都有默认优先级,主线程默认优先级是5;
如果A线程创建了B线程,那么B线程与A线程有相同优先级。
作用不是很明显
Thread thread1 = new Thread(){
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread() + "-----111");
}
}
};
Thread thread2 = new Thread(){
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread() + "-----222");
}
}
};
thread1.setPriority(10);
thread2.setPriority(1);
thread1.start();
thread2.start();
}
- 线程安全
- 同步代码块
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步。
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码。
使用 synchronized 关键字加上一个锁对象来定义一段代码, 这就叫同步代码块。
多个同步代码块如果使用相同的锁对象, 那么他们就是同步的。
// 同步代码块
synchronized (this) {
// 多个线程共享的代码
}
private static void test(){
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
PrinterTest.print1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
PrinterTest.print2();
}
}
}).start();
}
class PrinterTest {
ThreadDemo threadDemo = new ThreadDemo();
public static void print1() {
synchronized(threadDemo){ //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
System.out.print("你");
System.out.print("是");
System.out.print("猪");
System.out.print("吗");
System.out.print("\r\n");
}
}
public static void print2() {
synchronized(threadDemo){
System.out.print("无");
System.out.print("糖");
System.out.print("口");
System.out.print("香");
System.out.print("糖");
System.out.print("\r\n");
}
}
}
- 同步方法
使用synchronized修饰的方法。
// 同步方法
synchronized private void doWork () {
// 多个线程共享的代码
}
class PrinterTest {
public static void print1() {
synchronized(PrinterTest.class){ //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
System.out.print("你");
System.out.print("是");
System.out.print("猪");
System.out.print("吗");
System.out.print("\r\n");
}
}
/*
* 非静态同步函数的锁是:this
* 静态的同步函数的锁是:字节码对象
*/
public static synchronized void print2() {
synchronized(PrinterTest.class){
System.out.print("无");
System.out.print("糖");
System.out.print("口");
System.out.print("香");
System.out.print("糖");
System.out.print("\r\n");
}
}
public synchronized void print3() {
synchronized(this){
System.out.print("金");
System.out.print("钢");
System.out.print("狼");
System.out.print("\r\n");
}
}
}
-
死锁
多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁。 -
单例
保证类在内存中只有一个对象。 -
饿汉式
/**
* 单例模式的实现:饿汉式,线程安全 但效率比较低
*/
public class SingletonTest {
// 定义一个私有的构造方法
private SingletonTest() {
}
// 将自身的实例对象设置为一个属性,并加上Static和final修饰符
private static final SingletonTest instance = new SingletonTest();
// 静态方法返回该类的实例
public static SingletonTest getInstancei() {
return instance;
}
}
- 懒汉式
/**
* 单例模式的实现:懒汉式,线程安全
*/
public class SingletonTest {
// 定义私有构造方法(防止通过 new SingletonTest()去实例化)
private SingletonTest() {
}
// 定义一个SingletonTest类型的变量(不初始化,注意这里没有使用final关键字)
private static SingletonTest instance;
// 定义一个静态的方法(调用时再初始化SingletonTest,使用synchronized 避免多线程访问时,可能造成重的复初始化问题)
public static synchronized SingletonTest getInstance() {
if (instance == null)
instance = new SingletonTest();
return instance;
}
}
- 静态内部类法
public class Singleton{
private static class SingletonHolder{
public static Singleton instance = new Singleton();
}
private Singleton(){
}
public static Singleton getSingleton(){
return SingletonHolder.instance;
}
}
- 双重检查锁
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
- 枚举法
public enum Singleton {
INSTACE;
private String name;
public String getName() {
return name;
}
}
- Timer
public class TimerDemo {
public static void main (String[] args) {
// 3秒后执行
new Timer().schedule(new MyTimerTask(), 3000);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println("匿名内部类方式");
}
},2000);
// 周期执行
// 延迟3秒,每1秒执行一次
new Timer().schedule(new MyTimerTask(), 3000, 1000);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println("每隔1秒定时任务");
}
},0,1000);
}
}
class MyTimerTask extends TimerTask {
@Override
public void run() {
System.out.println("Timer test");
}
}
- 线程通信
wait():执行该方法线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他线程唤醒该线程。
notify():执行该方法的线程唤醒在等待池中等待的任一线程,把线程转到锁池中等待。
notifyAll():执行该方法的线程唤醒在等待池中等待的所有线程,把线程转到锁池中等待。
这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用。
public class ThreadCommunicationDemo {
public static void main(String[] args){
MyPrinter myPrinter = new MyPrinter();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
myPrinter.print1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
myPrinter.print2();
}
}
}).start();
// 三个线程通信
new Thread(new Runnable() {
@Override
public void run() {
while (true){
myPrinter.print3();
}
}
}).start();
}
}
class MyPrinter {
private int flag = 1;
public void print1() {
synchronized(this){ //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
// 两个线程通信
/*if (flag != 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
while (flag != 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("你");
System.out.print("是");
System.out.print("猪");
System.out.print("吗");
System.out.print("\r\n");
flag = 2;
// 两个线程通信
// this.notify(); // 随机唤醒单个线程
// 三个线程通信
this.notifyAll();
}
}
public synchronized void print2() {
synchronized(this){
/*if (flag != 2){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
while (flag != 2){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("无");
System.out.print("糖");
System.out.print("口");
System.out.print("香");
System.out.print("糖");
System.out.print("\r\n");
// 两个线程通信
// flag = 1;
// this.notify();
// 三个线程通信
flag = 3;
this.notifyAll();
}
}
public synchronized void print3() {
synchronized(this){
while (flag != 3){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("金");
System.out.print("钢");
System.out.print("狼");
System.out.print("\r\n");
flag = 1;
this.notifyAll();
}
}
}
如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件。
注意事项:
1.在同步代码块中用哪个对象锁,就用哪个对象调用wait 方法。
2.sleep方法必须传入参数,参数就是时间,时间到了自动醒来,
wait 方法可以传入参数也可以不传入参数。
3.sleep方法在同步函数或同步代码块中,不释放锁;
wait 方法在同步函数或同步代码块中,释放锁。
- 互斥锁
1)使用Lock机制取代synchronized代码块和synchronized方法。
2)使用Condition接口对象的await()和signalAll()方法等待和唤醒线程。
1.同步
使用ReentrantLock类的lock()和unlock()方法进行同步
2.通信
使用ReentrantLock 类的 newCondition() 方法可以获取Condition对象;
需要等待的时候使用 Condition的 await() 方法, 唤醒的时候用 signal() 方法;
不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了;
public class ReentrantLockDemo {
public static void main(String[] args) {
final MyPrinter1 myPrinter = new MyPrinter1();
new Thread() {
public void run() {
while(true) {
myPrinter.print1();
}
}
}.start();
new Thread() {
public void run() {
while(true) {
myPrinter.print2();
}
}
}.start();
new Thread() {
public void run() {
while(true) {
myPrinter.print3();
}
}
}.start();
}
}
class MyPrinter1 {
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private int flag = 1;
public void print1() {
r.lock(); // 获取锁
if(flag != 1) {
try {
c1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("你");
System.out.print("是");
System.out.print("猪");
System.out.print("吗");
System.out.print("\r\n");
flag = 2;
c2.signal();
r.unlock(); // 释放锁
}
public void print2() {
r.lock();
if(flag != 2) {
try {
c2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("金");
System.out.print("钢");
System.out.print("狼");
System.out.print("\r\n");
flag = 3;
c3.signal();
r.unlock();
}
public void print3() {
r.lock();
if(flag != 3) {
try {
c3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("无");
System.out.print("糖");
System.out.print("口");
System.out.print("香");
System.out.print("糖");
System.out.print("\r\n");
flag = 1;
c1.signal();
r.unlock();
}
}
-
线程组(了解)
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup() // 通过线程对象获取它所属于的组
public final String getName() // 通过线程组对象获取他组的名字
也可以给线程设置分组
1.ThreadGroup(String name) 创建线程组对象并给其赋值名字
2.创建线程对象
3.Thread(ThreadGroup?group, Runnable?target, String?name)
4.设置整组的优先级或者守护线程
public class ThreadGroupDemo {
public static void main(String[] args) {
threadGroup();
myThreadGroup();
}
public static void threadGroup() {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "张三");
Thread t2 = new Thread(mr, "李四");
// 获取线程组
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 线程默认情况下属于main线程组
// 默任情况下,所有的线程都属于同一个组
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
public static void myThreadGroup(){
ThreadGroup tg = new ThreadGroup("我是一个新的线程组"); //创建新的线程组
MyRunnable mr = new MyRunnable(); //创建Runnable的子类对象
Thread t1 = new Thread(tg, mr, "张三"); //将线程t1放在组中
Thread t2 = new Thread(tg, mr, "李四"); //将线程t2放在组中
System.out.println(t1.getThreadGroup().getName()); //获取组名
System.out.println(t2.getThreadGroup().getName());
// 通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "...." + i);
}
}
}
-
线程的生命周期
线程的生命周期
1、新建状态(new)
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
2、就绪状态(ready)
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
3、运行状态(running)
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
4、阻塞状态(blocked)
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
阻塞状态只能先进入就绪状态,不能直接进入运行状态。
5、死亡状态(terminated)
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
-
线程池(了解)
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
/*JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
*/
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
-
工厂模式
工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性。
// 动物抽象类
public abstract class Animal {
public abstract void eat();
}
// 工厂接口
public interface Factory {
public Animal createAnimal();
}
// 具体狗类
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
// 具体猫类
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
// 狗工厂
public class DogFactory implements Factory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
// 猫工厂
public class CatFactory implements Factory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
// 使用
public class Test {
public static void main(String[] args) {
DogFactory df = new DogFactory();
Dog d = (Dog) df.createAnimal();
d.eat();
}
}