1、多线程
进程:是一个正在执行的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。
一个进程中至少有一个线程。
java VM 启动的时候会有一个进程 java.exe
该进程中至少有一个线程,负责java程序的执行,而且这个线程运行的代码存在于main方法中。该线程称之为主线程
编译时:javac 类名.java 此时创建javac.exe进程,编译完毕后结束
运行时:java 类名 此时创建java.exe进程,运行完毕后结束
扩展:其实更细节说明JVM,jvm启动不止一个线程,除了主线程还有负责垃圾回收机制的线程。
2、如何在自定义代码中自定义一个线程,java已经提供了对线程这类事物的描述,就是Thread类。
第一种创建线程的方法:
定义类继承Thread
复写Thread类中的run方法
调用线程的start方法,该方法两个作用,启动线程,调用run()方法
发现运行结果每一次都不同,因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。
明确一点:在某一个时刻,只有一个程序在运行(多核除外),cpu在做着快速的切换,以达到看上去是同时运行的效果,我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这就是多线程的一个特性,随机性,谁抢到谁执行,至于执行多长,cpu决定。
为什么覆盖run方法?将自定义的代码存储在run中,以供线程运行。
Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。也就是说Thread类中run方法,用于存储线程要运行 的代码。 如果不复写父类的run方法,那么start时,调用的run就没有任何内容。
主线程要运行的代码存放在main方法中,这就是为什么JVM要调用main。
线程都有自己默认的名称,Thread -编号,编号从0开始
static Thread currentThread();获取当前线程对象
getName();获取线程名称
setName();自定义线程名称,或者构造函数
class Demo extends Thread
{
Test(String name)
{
super(name);//构造函数,自定义线程名称
}
//手动开启的线程执行run
public void run()
{
for(int i=1;i<133;i++)
{
System.out.println("d 执行我");
}
System.out.println("线程名称"+this.getName());//获得线程名称
System.out.println(Thread.currentThread()==this);//方法currentThread();是static的,返回值为运行当前线程的对象。
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();//创建一个线程
/*d.run();注意这里,没有start,对象直接调用run,那么就直接执行run内容,执行完run后,再执行后面的代码,此时执行run的是主线程,而不是开启的线程d。因为虽然Demo d = new Demo();创建了线程,但是没有开启。start的作用就是开启线程,然后调用run方法。*/
d.start();//使线程开启,并运行run()方法
d.start();//一个线程只能start一次,不能多次,
d.start();//因为线程start以后,再start没什么意义。
//主线程执行这个循环
for(int i=1;i<133;i++)
{
System.out.println("主线程执行我");
}
}
}
第二种创建线程的方法:实现Runnable接口
步骤:
1定义类实现Runnable接口
2覆盖Runnable接口中的run方法
将线程要运行的代码存放在run方法中
3通过Thread类建立线程对象
4将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
为什么?因为自定义的run方法所属的对象是Runnable接口子类对
象。所以要让线程去执行指定对象的run方法,就必须明确该run方法
所属对象。
5调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
class Ticket implements Runnable
{
private int tic=100;
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName()+"--“+tic--);
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);//创建一个线程
Thread t2 = new Thread(t);//创建一个线程
Thread t3 = new Thread(t);//创建一个线程
Thread t4 = new Thread(t);//创建一个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
实现方式与继承方式的区别:
实现方式好处:避免了单继承的局限性。(理解:如果一个类继承了Thread,同时还需要继承其他的类,这是用继承Thread就不行了,有局限,但一个类可以实现多个接口,因此可以使用实现Runnable的方式)在定义线程时,建议使用实现方式
两种方式区别:
继承Thread:线程代码存放在Thread子类的run方法中
实现Runnable:线程代码存放在接口子类的run方法中。
3、线程运行的状态
五种运行状态:
被创建:创建一个新的线程
运行状态:start新建的线程,进入运行状态
冻结状态:线程不被cpu执行,放弃执行资格
临时状态(或称为阻塞状态)
消亡:线程结束
几个关键方法:
start();表示启动线程并调用run()。
sleep(time);表示线程sleep time时间,时间到自动又具备执行资格
wait();表示线程进入无限期冻结状态,无执行资格
notify();表示唤醒冻结状态的线程
stop();表示消亡线程。
状态之间关系,用关键方法连接:
被创建——start()——运行状态
运行状态——stop()——消亡状态
运行状态——run()执行完毕——消亡状态
运行状态——sleep(time)——冻结状态——sleep时间到——
阻塞状态
运行状态——wait()——冻结状态——notify()——
阻塞状态(非运行)
运行状态——阻塞状态(理解:假设有4个线程,CPU在某一时刻只能执行一个线程,因此其他3个线程处于阻塞状态,表示有执行资格但没有cpu当前执行权)
4、多线程的安全问题
问题原因:当多条语句再操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中其他不可以参与执行。
java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。
synchronized(对象)
{
需要被同步的代码;
}
另一种方式,将synchronized关键字修饰函数,称为同步函数,具有锁的特性。
public
synchronized void show(){}
函数需要被对象调用,那么函数都有一个所属对象引用,就是this。所以同步函数使用的锁是this。
class Ticket implements Runnable
{
private int tick = 100;
public void run()
{
while(true)
{
this. show();//必须加this
}
}
public
synchronized void show()
{
if(tick>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName+tick--);
}
}
}
class StaticMethDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
对象如果锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。(火车上的卫生间——典型)
同步的前提:
必须要有两个或者两个以上的线程。
必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在执行
好处:解决了多线程的安全问题
弊端:多个线程都需要判断锁,较为消耗资源。
class Ticket implements Runnable
{
private int tic=100;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if (tic>0)
{
try(Thread.sleep(20);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+tic--);
}
}
}
}
}
静态同步函数:静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名 .class 该对象的类型时class 。静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class
class Ticket implements Runnable
{
private
static
int tick = 100;
boolean flag = true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(
Ticket.class)//静态同步函数使用的是类的字节码文件对象
{
if(tick>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName+tick--);
}
else
{
while(true)
{
show();
}
}
}
}
}
}
public
static synchronized void show()
{
if(tick>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName+tick--);
}
}
}
class StaticMethDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
5、多线程-单例设计模式-懒汉式
/*
饿汉式
class Single
{
private static final Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
*/
懒汉式:同步函数方式:加了synchronized后,每次线程都判断锁,因此比较低效。
class Single
{
private static Single s = null;
private Single(){}
public static
synchronized
Single getInstance()
{
if(s==null)
s= new Single();
return s;
}
}
懒汉式:同步代码块方式:优点在于稍微提高了点效率,双重判断s==null,减少了多个线程判断锁的次数。
注意:该代码,在面试时,课程为 延迟加载的单例设计模式。
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;
}
}
注意:面试问题
问:饿汉式与懒汉式有什么不同?
答:懒汉式特点在于实例的延迟加载
问:延迟加载有没有问题?
答:有,如果多线程访问时,会出现安全问题。
问:怎么解决?
答:用同步解决,同步函数或同步代码块,只不过同步代码块方式采用双重判断方式,减少了多个线程判断锁的次数,稍微提高了效率。
问:同步代码块时,使用的锁是哪个?
答:该类的字节码文件对象。
6、死锁,同步中嵌套同步
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.locka)
{
System.out.println("if locka");
synchronized(MyLock.lockb)
{
System.out.println("if lockb");
}
}
}
else
{
synchronized(MyLock.lockb)
{
System.out.println("else lockb");
synchronized(MyLock.locka)
{
System.out.println("else locka");
}
}
}
}
}
class MyLock
{
static MyLock locka = new MyLock();//定义锁locka
static MyLock lockb = new MyLock();//定义锁lockb
}
class DeadLockDemo
{
public static void main(String[] args)
{
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
死锁理解:
线程t1 start()时,调用new Test(true);的run方法;
线程t2 start()时,调用new Test(false)的run方法;
这里调用的不是同一个Test对象的run方法;
锁locka 和锁lockb是唯一的,不能同时被两个线程共同使用;意思就是locka只能被线程t1或t2使用,lockb只能被线程t1或t2是使用;而不能是locka即被线程t1使用又被线程t2使用。
但是一个线程可以使用多个锁,也就是说线程t1在使用locka的同时还可以使用lockb;t2在使用locka的同时也还可以使用lockb,但某一时刻,locka或lockb只能被一个线程使用。
synchronized(locka),表示其所属线程正使用锁locka;
synchronized(lockb),表示其所属线程正使用锁lockb;
代码解析:
运行结果:
if locka
else lockb
分析:
t1.start()时, 执行到System.out.println("if locka");打印if locka; 若想继续执行synchronized(MyLock.lockb),则必须拿到lockb;
而此时,由于t2.start(),执行属于new Test(false)对象的run()方法,执行到 System.out.println("else lockb");打印else lockb;若想继续执行 synchronized(MyLock.locka),则必须拿到locka;
关键是: synchronized(MyLock.locka){}中代码还没执行完,因此t1不可能放掉锁locka;synchronized(MyLock.lockb){}中代码还没执行完,因此t2也不可能放掉lockb;而locka 和lockb只能在某一时刻被一个线程拿到,t1在拿到locka的同时还想拿到lockb,t2在拿到lockb的同时还想拿到locka,t1 t2又都不能放弃各自的锁,因此程序陷入僵局。也就是形成了死锁。
class Ticket implements Runnable
{
private int tic=100;
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName()+"--“+tic--);
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);//创建一个线程
Thread t2 = new Thread(t);//创建一个线程
Thread t3 = new Thread(t);//创建一个线程
Thread t4 = new Thread(t);//创建一个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable
{
private int tick = 100;
public void run()
{
while(true)
{
this. show();//必须加this
}
}
public
synchronized void show()
{
if(tick>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName+tick--);
}
}
}
class StaticMethDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
class Ticket implements Runnable
{
private int tic=100;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if (tic>0)
{
try(Thread.sleep(20);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+tic--);
}
}
}
}
}
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;
}
}