多线程
一、多线程

答:进程是一个正在执行中的程序,每一个进程的执行,都有一个执行的顺序,该顺序就是一个执行路径,或者叫一个控制单元。

答:线程就是进程中一个独立的控制单元,线程在控制着进程的执行,一个进程中至少有一个线程。
Java VM启动时会有一个进程java.exe,该进程中至少有一个线程负责Java程序的执行,而且这个线程运行的代码存在于main()方法中,该线程称为主线程。
扩展:其实,JVM启动不止一个线程,还有负责垃圾回收机制的线程。

答:通过对Api文档的查找,Java已经提供了对线程这类事物的描述,就是Thread类。
步骤:
1、定义类继承Thread。
2、复写Thread类中的run()方法。目的:将自定义的代码存储在run方法中,让线程运行。
3、调用线程的start()方法。该方法两个作用:启动线程;调用run()方法。
例:
class Demo extends Thread
{
public void run(){
for(int x=0;x<60;x++){
System.out.println("demo run "+x);
}
}
}
class ThreadDemo
{
public static void main(String []args){
Demo d = new Demo();
d.start();
for(int x=0;x<60;x++){
System.out.println("Hello World!—"+x);
}
}
}
发现:运行结果每一次都不同。因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行。明确一点:在某一时刻,只能有一个程序在运行,多核除外。CPU在做着快速切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行形容为在互相抢夺CPU执行权,这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,CPU说的算。

答:Thread类用于描述线程。该类线程就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run()方法。也就是说,Thread类中的run()方法用于存储线程要运行的代码。
start:开启线程并执行该线程的run()方法,调用底层。
练习:创建两个线程,和主线程交替运行。
class Test extends Thread
{
String name;
Test(String name){
this.name = name;
}
public void run(){
for(int x=0;x<60;x++){
System.out.println(name+"--->"+x);
}
}
}
class Demo
{
public static void main(String []args){
Test t1 = new Test("one");
Test t2 = new Test("two");
t1.start();
t2.start();
for(int x=0;x<60;x++){
System.out.println("main"+"------>"+x);
}
}
}
创建线程方式一:
继承Thread类
1.子类覆盖父类中的run()方法,将线程运行代码存放在run()方法中。
2.建立子类对象的同时,线程也被创建。
3.通过调用start方法开启线程。
线程的四种状态:
临时状态:具备运行资格,但没有执行。
冻结:放弃了执行资格。
sleep()方法:需要指定睡眠时间,单位是毫秒。
一个特殊的状态:就绪,具备了执行资格,但是还没有获取资源。
线程的名称:线程都有自己默认的名称:Thread-编号。该编号从0开始。
currentThread():返回对当前正在执行的线程对象的引用。
getName():返回该线程的名称。
应用:
Thread.currentThread().getName();//这样可以获取当前运行线程的名字。
需求:简单的卖票程序,多个窗口同时卖票。
class Ticket implements Runnable
{
//100张票
int tickets = 100;
public void run(){
while(tickets>0){
System.out.println(Thread.currentThread().getName()+"sale.."+tickets--);
}
}
}
class Demo
{
public static void main(String []args){
Ticket ti = new Ticket();
//窗口1
Thread t1 = new Thread(ti);
//窗口2
Thread t2 = new Thread(ti);
//窗口3
Thread t3 = new Thread(ti);
//窗口4
Thread t4 = new Thread(ti);
//窗口1开始卖
t1.start();
//窗口2开始卖
t2.start();
//窗口3开始卖
t3.start();
//窗口4开始卖
t4.start();
}
}
创建线程方式二:实现Runnable接口。
步骤:
1.定义类实现Runnable接口。
2.覆盖Runnable接口中的run()方法(将线程要运行的代码存放在该run()方法中)。
3.通过Thread类建立线程对象。
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

答:因为,自定义的run()所属的对象是Runnable接口的子类对象。所以要让线程去执行指定对象的run()方法。就必须明确该run()方法所属对象。
5.调用Thread类的start()方法开启线程,并调用Runnable接口子类的run()方法。

答:采用实现的方式使得类的扩展能力加强,除了可以继承其他类,还能实现多个接口。线程代码存放在Thread子类的run()方法中。而采用继承的方式,再想继承别的类就不行了。所以说开发中建议使用继承式。线程代码存放在接口的子类run()方法中。
实现方式的好处:避免了单继承的局限性。
多线程的安全问题:当线程多的时候,CPU的切换可能会导致程序出问题。
技巧:利用sleep()放慢模拟CPU切换。
class Ticket implements Runnable
{
//100张票
int tickets = 100;
public void run(){
while(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"sale.."+tickets--);
}
}
}
class Demo
{
public static void main(String []args){
Ticket ti = new Ticket();
//窗口1
Thread t1 = new Thread(ti);
//窗口2
Thread t2 = new Thread(ti);
//窗口3
Thread t3 = new Thread(ti);
//窗口4
Thread t4 = new Thread(ti);
//窗口1开始卖
t1.start();
//窗口2开始卖
t2.start();
//窗口3开始卖
t3.start();
//窗口4开始卖
t4.start();
}
}
分析:当线程运行到Thread.sleep(100);的时候,线程处于冻结状态,这时其他线程抢到了CPU的执行权开始运行,直到把票卖完,该线程结束。这时原来处于冻结状态的的线程恢复了运行,接着执行。就出现了错票(负值)。
解决办法:当多条语句在操作同一个线程的共享数据时,一个线程对多条语句只执行一部分,还没有执行完。另一个线程参与进来执行,导致共享数据的错误。对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。
synchronized(对象){
需要被同步的代码;
}
class Ticket implements Runnable
{
int tickets = 100;
Object obj = new Object();
public void run(){
while(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
synchronized(obj){
if(tickets>0)
System.out.println(Thread.currentThread().getName()+"sale.."+tickets--);
}
}
}
}
class Demo
{
public static void main(String []args){
Ticket ti = new Ticket();
Thread t1 = new Thread(ti);
Thread t2 = new Thread(ti);
Thread t3 = new Thread(ti);
Thread t4 = new Thread(ti);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
原理:synchronized(obj)中的obj相当于锁,持有锁的线程可以在同步中执行,没有持有锁的线程,即使获取到了CPU的执行权,也进不去。当0线程进来的时候,锁状态为标志由1变为0。然后0线程sleep。这时1线程进来了,判断锁状态,发现为0,进不来。0醒了,执行完后出同步,把标志由0变为1。这时1线程如果拿到了执行权就可以进来了。
同步的前提:
1.必须要有两个或者两个以上的线程。
2.必须是多个线程使用同一个锁。
3.必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
需求:银行有一个金库,有两个储户分别存300元,每次存100,存3次。
class Bank
{
private int sum;
public void add(int n){
sum = sum+n;
try{Thread.sleep(100);}catch(Exception e){}
System.out.println("sum = "+sum);
}
}
class Cus implements Runnable
{
private Bank b = new Bank();
public void run(){
for(int x=0;x<3;x++){
b.add(100);
}
}
}
class Demo
{
public static void main(String []args){
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
目的:该程序是否有安全问题,如果有,如何解决?
1.明确哪些代码是多线程运行代码。
2.明确共享数据。
3.明确多线程运行代码中哪些语句是操作共享数据的。
分析:当程序运行完sum = sum+n;后,CPU切换了,这时另一线程进来了,又做了一次sum = sum+n;。然后CPU又切换到上一个线程,这时打印的是两个线程执行完累加的结果。这就出现了问题。
解决办法:运用同步代码块将操作共享数据的语句封装起来。
class Bank
{
private int sum;
Object obj = new Object();
public void add(int n){
synchronized(obj){
sum = sum+n;
try{Thread.sleep(100);}catch(Exception e){}
System.out.println("sum = "+sum);
}
}
}
class Cus implements Runnable
{
private Bank b = new Bank();
public void run(){
for(int x=0;x<3;x++){
b.add(100);
}
}
}
class Demo
{
public static void main(String []args){
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
也可以使用同步函数,将函数用
synchronized关键字修饰。
class Bank
{
private int sum;
Object obj = new Object();
public synchronized void add(int n){
sum = sum+n;
try{Thread.sleep(100);}catch(Exception e){}
System.out.println("sum = "+sum);
}
}
class Cus implements Runnable
{
private Bank b = new Bank();
public void run(){
for(int x=0;x<3;x++){
b.add(100);
}
}
}
class Demo
{
public static void main(String []args){
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
同步的两种
表现形式:
同步函数和
同步代码块。
练习:验证同步函数用的锁是this锁。
采用Object锁
class Ticket implements Runnable
{
int tickets = 100;
boolean flag = true;
Object obj = new Object();
public void run(){
if(flag){
while(tickets>0){
synchronized(obj){
if(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"sale-----------"+tickets--);
}
}
}
}
else{
while(tickets>0){
show();
}
}
}
public synchronized void show(){
if(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"sale-----"+tickets--);
}
}
}
class Demo
{
public static void main(String []args){
Ticket ti = new Ticket();
Thread t1 = new Thread(ti);
Thread t2 = new Thread(ti);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
ti.flag = false;
t2.start();
}
}
采用this锁
class Ticket implements Runnable
{
int tickets = 100;
boolean flag = true;
Object obj = new Object();
public void run(){
if(flag){
while(tickets>0){
synchronized(this){
if(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"sale-----------"+tickets--);
}
}
}
}
else{
while(tickets>0){
show();
}
}
}
public synchronized void show(){
if(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"sale-----"+tickets--);
}
}
}
class Demo
{
public static void main(String []args){
Ticket ti = new Ticket();
Thread t1 = new Thread(ti);
Thread t2 = new Thread(ti);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
ti.flag = false;
t2.start();
}
}
比较两个锁,发先只有this锁的程序没有出问题。所以说明同步函数使用的是this锁。出问题是因为两这个用的锁不同,不存在限制的问题。用的所相同时,会判断锁,获取不到锁,即使有运行资格,也不会运行。

答:函数要被对象调用,每个函数都有一个所属对象的引用,那就是this。

class Ticket implements Runnable
{
static int tickets = 100;
boolean flag = true;
Object obj = new Object();
public void run(){
if(flag){
while(tickets>0){
synchronized(this){
if(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println("sale------"+tickets--);
}
}
}
}
else{
while(tickets>0){
show();
}
}
}
public static synchronized void show(){
if(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println("sale------------"+tickets--);
}
}
}
class Demo
{
public static void main(String []args){
Ticket ti = new Ticket();
Thread t1 = new Thread(ti);
Thread t2 = new Thread(ti);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
ti.flag = false;
t2.start();
}
}
答:通过验证,发现不再是this锁了,因为静态方法中也不可能有this。静态先进入内存,这时还没有对象,所以不应该是this锁,但是一定用该类对应的字节码文件对象。类名.class该对象的类型是class。
采用Ticket.clss锁:
class Ticket implements Runnable
{
static int tickets = 100;
boolean flag = true;
Object obj = new Object();
public void run(){
if(flag){
while(tickets>0){
synchronized(Ticket.class){
if(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println("sale------"+tickets--);
}
}
}
}
else{
while(tickets>0){
show();
}
}
}
public static synchronized void show(){
if(tickets>0){
try{Thread.sleep(100);}catch(Exception e){}
System.out.println("sale------------"+tickets--);
}
}
}
class Demo
{
public static void main(String []args){
Ticket ti = new Ticket();
Thread t1 = new Thread(ti);
Thread t2 = new Thread(ti);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
ti.flag = false;
t2.start();
}
}
结论:静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class。同步代码块使用的锁是this锁。
复习:单例模式——懒汉式(面试)
class Test
{
private static Test t = null;
private Test(){}
public static Test getTest(){
if(t==null){
synchronized(Test.class){
if(t==null){
t = new Test();
}
}
}
return t;
}
}
class Demo
{
public static void main(String []args){
Test t1 = Test.getTest();
Test t2 = Test.getTest();
System.out.println(t1==t2);
}
}
懒汉式采用双重判断的方式减少判断同步锁,优化了执行效率,不必每次都对锁进行判断。
死锁(重点):死锁通常发生在同步中嵌套同步。
例:
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
{
public static Object locka = new Object();
public static Object lockb = new Object();
}
class Demo
{
public static void main(String []args){
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}