一、什么是多线程?
1、进程
进程是指正在运行的程序,但是cpu执行的并不是进程而是线程。
2、线程
线程是进程内一个相对独立的、可调度的执行单元或控制单元。操作系统可执行的最小单位是线程。一个进程中至少一个线程。
3、多线程
线程在操作系统中是可以并发运行的,这样可以充分利用外围设备。以java.exe为例,该进程至少包含两个线程,一个是执行代码的线程,另一个是回收和释放内存的线程。他们是并发执行的,我们称一个进程中多个线程运行的为多线程。
4、多线程的意义(多线程与cpu的关系)
目前我们常用的操作系统为分时系统,这类操作系统的特点是在极短时间内在不同的程序间切换执行,让我们看起来就像是很多程序同时运行。而多线程的存在就可以提升cup的切换速度,提高执行效率。这里需注意的是cpu在切换线程执行是随机的,具体下面有实例
二、线程的几种状态
1、就绪:线程获取了除cpu意外的全部资源,等待CPU的调度。
2、执行:线程获得CPU执行权限,正在执行。
3、挂起(休眠):因为终端请求或者操作系统要求,线程停止运行,被挂起。挂起的线程不能自己苏醒,必须被其他线程唤醒。
4、阻塞:线程因为I/O等操作导致无法运行,转到就绪状态。
5、消亡:stop()或者进程结束。
三、java的两种线程创建方式
1、将类声明为 Thread
的子类(继承的方式)
创建方法:
1)定义一个demo类继承Thread类。
2)复写Thread类的run方法。
理由:run中存放的是自定义运行的代码
3)调用线程的start方法。
作用:1.启动线程 ;2.调用run方法。
程序示例:
package exam1;
/* 创建两个线程,让其与主程序交替运行。
* 1)定义一个demo类继承Thread类。
* 2)复写Thread类的run方法。
* 理由:run中存放的是自定义运行的代码
* 3)调用线程的start方法。
* 作用:1.启动线程 ;2.调用run方法。
*/
class demo extends Thread//定义一个demo类继承Thread类。
{
//private String name;
demo(String name)
{
//this.name = name;
super(name);
}
public void run()//复写Thread类的run方法,其中主要存放线程需要执行的代码
{
for(int i=0; i<60;i++)
// System.out.println(name+" run!!---"+i);
//currentThread() 获取当前线程的对象
//getName()获取当前线程的名称
System.out.println(this.currentThread().getName()+"run!--"+i);
}
}
public class Test1
{
public static void main(String[] args)
{
demo t1 = new demo("one++++");
demo t2 = new demo("two----");
// t1.run();//直接调用方法,并不新建一个进程
// t2.run();//
t1.start();//调用线程的start方法,新建一个进程并在进程中调用方法。
t2.start();
for(int i = 0; i < 100; i++)
System.out.println("Main---!!"+i);
}
}
执行结果:
从结果中可以看出:同一个进程的几个线程在执行过程中,是随机交替执行的。
2、声明实现 Runnable
接口的类
* 步骤:
* 1.定义类实现Runnable接口。
* 2.覆盖Runnable接口中的RUN方法。
* 3.通过Thread类建立线程对象。
* 4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
package exam1;
/*
* 需求:简单的卖票程序。(多线程,实现Runnable接口)
* 多窗口同时买票。
*
*
* 步骤:
* 1.定义类实现Runnable接口。
* 2.覆盖Runnable接口中的RUN方法。
* 3.通过Thread类建立线程对象。
* 4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
*
*/
class Ticket implements Runnable//实现Runnable接口
{
private int Tick = 100;
public void run()
{
while(Tick>0)
{
System.out.println(Thread.currentThread().getName()+" run---"+Tick--);
}
}
}
public class Test2
{
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);
Thread t5 = new Thread(T);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
/*---继承的方式购票系统--*/
/*class Ticket extends Thread
{
private static int Tick = 100;//
public void run()
{
while(Tick>0)
{
System.out.println(currentThread().getName()+"....."+Tick--);
}
}
}
public class Test2
{
public static void main(String[] args)
{
Ticket T1 = new Ticket();
Ticket T2 = new Ticket();
Ticket T3 = new Ticket();
Ticket T4 = new Ticket();
Ticket T5 = new Ticket();
T1.start();
T2.start();
T3.start();
T4.start();
T5.start();
}
}*/
两种方式的联系和区别:继承方式的局限性是继承了Thread类用来创建线程,那么这个类就无法继承其他的类。而通过实现Runnable接口创建线程不存在这个限制。定义线程时候最好采用实现方式,避免java单继承的局限性。
四、安全问题
在运行上面的售票程序的时候出现了卖出了0号,-1号等错误的票。
1、出现上述问题的原因是当一个进程执行共享的数据时候,该语句执行一部分就停止了然后切换到了另一线程。这就是著名的生产者,消费者问题。
2、解决方案。
同步函数
格式:
在函数上加上synchronized修饰符即可。
那么同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
1)同步代码块。
示例:
* synchronized(object)
* {
* 需要同步的代码块。
* }
生产者消费者解释:参考http://blog.chinaunix.net/uid-21411227-id-1826740.html
package exam1;
/*
* 需求:简单的卖票程序。(多线程,实现Runnable接口)
* 多窗口同时买票。
*
*
* 步骤:
* 1.定义类实现Runnable接口。
* 2.覆盖Runnable接口中的RUN方法。
* 3.通过Thread类建立线程对象。
* 4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
* 注:run方法是存放线程运行代码的位置。
*/
/* 多线程出现安全问题:
* 产生原因:多个线程共享一个数据,且是多条语句操作时候。一个线程对多条语句只执行了一部分,并没有执行完,
* 此时另一个线程参与进来。
*
* 解决办法:对多条操作共享数据的语句,一个线程在执行此语句时候其他线程不允许操作。
* JAVA提供的解决方案:同步代码块。
* 示例:
* synchronized(object)
* {
* 需要同步的代码块。
* }
* 解释:火车上的厕所,进去一个人将锁锁上。这里的synchronized(object)就相当于厕所门。
* 同步的好处:解决了多线程的安全问题。
* 弊端:线程每次执行都会判断一次锁,比较消耗资源。
*
*/
class Ticket implements Runnable//实现Runnable接口
{
private int Tick = 100;
Object obj = new Object();//new一个同步代码块需要的对象
public void run()
{
while(true)
{
synchronized(obj)//同步代码块
{
if(Tick > 0)
{
try{Thread.sleep(10);}catch(Exception e){}//使用sleep()模拟真实情况,测试安全问题
System.out.println(Thread.currentThread().getName()+" sale---"+Tick--);
}
}
}
}
}
2)同步函数
示例:
public synchronized void function(参数)
{
}
跟同步代码块一样,同步函数也有一个所属对象引用,也就是一个锁(this)。
代码:
package exam1;
/* 需求: 银行金库。
* 有两个用户分别存300元,每次存100元,存三次。
*
* 目的:验证此程序有无安全问题?且如何解决。
*
* 如何找到问题?
* 1. 哪些带代码是多线程运行代码?
* 2. 哪些是共享数据?
* 3. 哪些语句是操作共享数据的?
*/
class Bank
{
private int sum;//金库的钱的总数。
public synchronized void add(int m)//同步函数
{
// synchronized (this)//同步代码块解决
// {
sum = sum + m;
try{Thread.sleep(10);}catch(Exception e){}//停10ms模拟
System.out.println("sum="+sum);
// }
}
}
class Cus implements Runnable
{
private Bank b = new Bank();//新建一个名为b的Bank对象。
public void run()
{
for(int i=0; i<3; i++)
{
b.add(100);
}
}
}
public class Test3
{
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,所以锁不再是this了,此时内存中虽然没有本类对象,但是一定有该类对应的字节码对象。
即:类名.class
代码:
package exam1;
/*
* 如果同步函数被 静态修饰后,使用的锁是什么呢?(不是this)
* 此时该对象的类型为 类名.class,使用的锁是该方法所在类的字节码文件对象。
*/
class Ticket2 implements Runnable//实现Runnable接口
{
private static int Tick = 100;
// Object obj = new Object();//new一个同步代码块需要的对象
boolean flag = true;
public void run()
{
while(true)
{
synchronized(Ticket2.class)//静态的同步方法
{
if(Tick > 0)
{
try{Thread.sleep(10);}catch(Exception e){}//使用sleep()模拟真实情况,测试安全问题
System.out.println(Thread.currentThread().getName()+" sale---"+Tick--);
}
}
}
}
}
}
六、单例设计模式。
//懒汉式,作用是延迟加载.这里很重要。
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()//此处因为s是共享的数据,而对其操作的是多条语句。
{
synchronized(Single.class)//同步代码块
{
if(s == null)
s = new Single();
}
return s;
}
}
代码:
package exam1;
/*@ 面试中恶汉式和懒汉式的区别。
*@
*@
*/
/*
* 单例设计模式
*/
//恶汉式
/*
class Single
{
private static final Single s = new Single();//final更加严谨
private Single(){}
public static Single getInstance()
{
return s;
}
}
*/
//懒汉式,作用是延迟加载。
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()//此处因为s是共享的数据,而对其操作的是多条语句。
{
synchronized(Single.class)//同步代码块
{
if(s == null)
s = new Single();
}
return s;
}
}
public class Test6 {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
七、死锁
两个进程同时持有自己的锁,然后去获取对方的锁,导致相互永远等待的情况,这称为死锁。
代码:
package exam1;
class Test_8 implements Runnable///实现Runnable接口(端口实现多线程)
{
private boolean flag;
Test_8(boolean flag)
{
this.flag = flag;
}
public void run() {
if(flag)
{
synchronized(Mylock.locka)//a锁内嵌套b锁
{
System.out.println("if locka");
synchronized(Mylock.lockb)
{
System.out.println("if lockb");
}
}
}
else
{
synchronized(Mylock.lockb)//b锁内嵌套a锁
{
System.out.println("if lockb");
synchronized(Mylock.locka)
{
System.out.println("if locka");
}
}
}
}
}
class Mylock//存储两个锁。
{
static Object locka = new Object();
static Object lockb = new Object();
}
public class Test8 {
public static void main(String[] args) {
Thread t1 = new Thread(new Test_8(true));
Thread t2 = new Thread(new Test_8(false));
t1.start();
t2.start();
}
}
运行结果:
这里看到锁上了,两个线程都持有对应的锁,且要获取对方的锁。导致程序锁死。