1.
进程:正在执行中的程序
线程:进程中一个独立的控制单元。 线程在控制进程的执行
一个进程中至少有一个线程。
主线程: JVM启动的时候会产生一个进程 java.exe,该进程中至少有一个线程负责java进程的执行,这个线程的代码存在于main方法中,称之为主线程。
JVM启动时,还会有负责垃圾回收的线程。
2.创建新线程有两种方法:
一种是将类声明为 Thread 的子类,并重写子类的 run 方法,分配并启动该子类的实例。
步骤:
定义类继承Thread
复写Thread类中的run方法。(将自定义的代码存储在run方法中,让线程运行)
调用线程的 start 方法。 (该方法启动线程,通过调用run)
3.为什么要覆盖 run()
Thread 用于描述线程,该类定义了一个函数,用于存储线程要运行的代码。该存储功能就是 run 方法。
start(); //开启线程并执行线程的run方法
run(); // 仅仅调用方法,线程创建了但没有运行
4.线程的运行状态:
5.线程名字获取
System.out.println(Thread.currentThread().getName()); //main
static Thread currentThread() 返回当前线程对象的引用
getName() 获取线程名称
setName() 或重写构造函数 改变线程名称
6 .创建线程的第2种方式是声明实现 Runnable 接口:
定义一个类,实现Runnable 接口
覆盖Runnable接口中的 run 方法
建立Thread类对象, 将Runnable的子类实例作为实际参数传递给Thread类的构造函数
调用Thread实对象的 start() 方法执行线程
实现方式与继承方式的区别:
实现方式:避免了单继承的局限性, 定义线程时,建议使用此方式
实现方式 线程代码存放在接口的子类的run方法中
继承方式 线程代码存放在Thread子类的run方法中
class Thread2 implements Runnable { private int ticket = 30; public void run() { while(ticket>0) { if(ticket>0) System.out.println("tickete="+ticket--); } } } class Main { public static void main(String [] args) { Thread2 th2 = new Thread2(); Thread th = new Thread(th2); th.start(); new Thread(th2).start(); new Thread(th2).start(); // 此处多个线程共享资源ticket } }
7. 多线程的安全问题:
当多个语句操作同一个线程共享数据,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来,导致共享数据错误。
java对多线程安全问题提供了专业的解决方式: 同步代码块
synchronized(对象){ 需要被同步的代码 }
对象如同锁,持有锁的线程可以在同步中执行,没有锁的线程即使有CPU执行权,也进不去。
同步的前提: 有2个以上的线程; 必须多个线程使用同一个锁;
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较消耗资源
8.同步的另一种方式: 同步函数
同步函数用的是哪一个锁呢?
函数因为需要被对象调用,那么函数都有一个所属对象引用,就是this,同步函数使用的锁是this
静态同步函数使用的锁是该函数所在类的字节码文件对象 类名.class
class Ticket implements Runnable { private static int ticket = 30; public void run() { while(ticket>0) { if(ticket>0) { show(); } } } static synchronized void show() //Ticket.class { try { Thread.sleep(10); } catch(Exception e){} System.out.println(Thread.currentThread().getName()+"tickete="+ticket--); } }
9.单例设计模式
//懒汉式: 单例设计模式,特点:实例延迟加载 class Single { private static Single s = null; private Single() {} public static Single getInstance()//多线程时会出现安全问题,可以加同步来解决 { if(s==null) //双重判断解决同步的效率问题 synchronized(Single.class)//锁用的该类所属的字节码文件对象 { if(s==null) s = new Single(); } return s; } } //饿汉式 class Single { private static final Single s = new Single(); private Single() {} public static Single getInstance() { return s; } }
10. 死锁示例:
class Test implements Runnable { public static boolean flag = true; public void run() { if(flag) { while(true) { synchronized(this) { System.out.println("线程1-1"); synchronized(Test.class) { System.out.println("线程1-2"); } } } } else { while(true) { synchronized(Test.class) { System.out.println("线程2-1"); synchronized(this) { System.out.println("线程2-2"); } } } } } } class Main { public static void main(String [] args) { Test t = new Test(); new Thread(t).start(); try { Thread.sleep(5); } catch(Exception e){} t.flag = false; new Thread(t).start(); } }
11. 同步唤醒机制:
wait( ) notify() notifyAll()
都使用在同步中,因为要对持有锁的线程操作。 只有同步才具有锁。
wait()会使锁中的线程释放锁。醒来之后,当再次分配到锁时,从刚才的位置继续执行
为什么这些操作线程的方法要定义在 Object 类中?
因为这些方法在操作线程时,都必须要标识他们所操作线程 持有的锁。
只有同一个锁上的被等待线程,可以被同一个锁上的 notify 唤醒, 不可以被不同锁中的notify唤醒。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在 Object 类中。
12. 生产者消费者模式:
对于多个生产者和消费者,为什么定义while 判断标记? 让被唤醒的线程再一次判断标记
为什么定义 notifyAll() 因为用notify可能出现唤醒本方线程的情况,导致程序中所有线程都等待。
JDK1.5中提供了多线程升级解决方案。(显式的锁机制)
将同步 synchronized 替换成现实 Lock 操作,
将 Object 中的wait notify notifyAll 替换成了 Condition 对象,该对象可以由Lock锁获取。
13. 停止线程、 守护线程
停止线程: stop()方法已过时
一种方法是在 run()方法中结束。 多线程的运行代码通常是循环结构,只要控制住循环,就可以让run 方法结束,也就是线程结束。
特殊情况,当线程处于冻结状态,读取不了标记,run方法无法结束进程,需要 Thread. interrupt()方法:
当调用了Object类的 wait 方法或者Thread 类的join 、 sleep 方法时,线程阻塞,
interrupt 方法将处于冻结状态的线程强制恢复到运行状态。
setDaemon(boolean on) 守护线程必须在启动线程前启用; 当前台线程都停止时,守护线程自动退出,JVM退出。
14. join 方法
当A线程的代码执行到了 B线程的 .join() 方法时,A就会等待,等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。
15. toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
线程默认优先级是 5 ,最小是1,最大是10。
class Main { public static void main(String [] args) { Thread.currentThread().setPriority(Thread.MAX_PRIORITY); System.out.println(Thread.currentThread().toString()); } }
优先级为10时,抢到执行权的频率高一些而已,其他线程也有机会抢到。
Thread.yield() 线程执行到yield方法时,暂停正在执行的线程,执行其他线程。
yield方法造成多个线程交替执行,相对避免了一个线程连续多次抢到执行权
16. 某些代码需要被同时执行时,就需要用单独的线程进行封装。