------- android培训、java培训、期待与您交流! --------
7 java多线程
进程:是一个正在执行中的程序,每一个进程都有一个执行顺序,该顺序是一个执行路径,
或者是一个控制单元,一个进程中至少有一个线程。
线程:是进程中的一个独立的控制单元,线程是控制着线程的执行。
主线程:java vm 启动的时候会有一个进程java.exe,该进程中至少一个线程负责java程序的运行,还有一个线程是负责垃圾回收机制
而且这个线程运行的代码是在main方法中,所以该线程称为主线程。
7.1.创建线程
1) 继承Thread。
1.定义类继承Thread。
2.复写Thread类中的run方法。
3.调用线程的start方法,该方法有两个作用:启动线程,调用run方法。
1)扩展thread类
语法格式:
public class YourThread extends Thread{
public void run(){
//希望执行的代码
}
}
调用格式:
public static void main(String[] args){
YourThread t=new YourThread();
t.start();
}
2) 实现runnable接口
1.定义类实现runnable接口
2.覆盖runnable接口中的方法,将要运行的代码放在run方法中。
3.通过Thread类创建一个线程实例。
4.将runnable接口的子类对象作为参数传给Thread类的构造函数,
因为自定义的run方法所属对象是runnable接口的子类对象,所以必须让线程去指定对象的run方法,就必有明确该run方法的所属对象。
5.调用Thread类的Start方法启动线程,并调用runnable接口的run方法。
语法格式:
public class YourThread implements Runnable{
public void run(){
//希望执行的代码
}
}
调用格式:
public static void main(String[] args){
YourThread t=new YourThread();
Thead tt=new Thread(t);
tt.start();
}
实现方式和继承方式有什么区别?
1.实现方式:将要运行的代码放在runnable接口子类run方法当中。
2.继承方式:将要运行的代码放在Thread类的run方法当中。
3.实现方式的好处:避免了单继承的局限性;因为java只支持单继承。
4.定义线程时,建议使用实现方式。
7.2 线程的同步
多线程产生安全问题的原因?
因为当多条语句共同操作同一线程的共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一线程
参与进来执行,导致共享数据的错误。
解决方法:
对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不可以参以执行。
1.方法同步
通过在方法声明加入Synchronized关键字来声明methodName方法:
public synchronized void methodName(parameterList){
..........
}
2.对象同步
语法格式:
synchronized (object){
//允许访问的控制代码
}
例如:
/*
* 使用二个线程来买票
* 一个线程在两步代码块中
* 一个在两步函数中
* 都是在执行买票
*/
public class Tick implements Runnable {
//假设有1000张票
private int tick=100;
@Override
public void run() {
while(true){
//(2)采用synchronized来给当前this(tick)对象加锁
// synchronized (this){
this.show();
// }
}
}
//(1)采用synchronized来声明show方法
public synchronized void show(){
//判断如果票数大于0
if(tick>0){
//让当前线程睡眠
try {Thread.sleep(10); } catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+".....sail:"+tick--);
}
}
public static void main(String[] args) {
Tick t = new Tick();
//创建二个线程
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//同时启动二个线程
t1.start();
t2.start();
}
}
3.对象如同锁,持有锁的线程可以在同步中执行,没有锁的线程即没有获取CPU的执行权,也进不去因为没有获取锁。
经典案例-火车上的卫生间。
4.同步的前提:
1.必须两个或者两个以上的线程。
2.必须是多个线程使用同一个锁。
5.同步的优点:解决了多线程的安全问题,弊端多个线程需要判断较为消耗资源。
6.同步函数使用是哪个锁?
函数需要被对象调用,那么函数只有一个所属对象引用,就是this。
7.如果同步函数被静态修饰后,使用的锁是什么?
通过验证,发现不在是this,因为静态方法中也不可以定义this
静态进内存是,内存中没有本类对象;但一定有该类的字节码文件对象,类名.class,该对象的类型是Class。
8.静态的同步方法,使用的锁是该方法所在类的字节码文件对象,类名.class。
如下 :
/*
* 使用二个线程来买票
* 一个线程在两步代码块中
* 一个在两步函数中
* 都是在执行买票
*/
public class Tick implements Runnable {
//假设有1000张票
private static int tick=100;
boolean flag=true;
@Override
public void run() {
if(flag){
while(true){
//该方法所在的类的字节码对象Tick.class
synchronized (Tick.class){
if(tick>0){
//让当前线程睡眠
try {Thread.sleep(10); } catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+".....code:"+tick--);
}
}
}
}else
while(true)
show();
}
//同步方法被静态修饰
public static synchronized void show(){
//判断如果票数大于0
if(tick>0){
//让当前线程睡眠
try {Thread.sleep(10); } catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+".....sail:"+tick--);
}
}
public static void main(String[] args) {
Tick t = new Tick();
//创建二个线程
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//同时启动二个线程
t1.start();
try {Thread.sleep(10); } catch (Exception e) {}
t.flag=false;
t2.start();
}
}
9.wait(),notify(),notifyAll()用来操作线程为什么定义在object类中。
1.这些方法存在与同步中。
2.使用这些方法时必须要标识所属的同步的锁。
3.锁是任意对象,所以任意对象调用的方法一定定义在Object类中。
10.wait(),sleep()的区别
wait()是object类是的方法,释放资源,释放锁,
只有通过对此对象发出notify(),notifyAll(),本线程才进入对象锁定池,准备获取对象锁进入运行状态。
sleep()是Thread类的方法,释放资源,不释放锁。
11.线程的通讯:多个线程共同访问一个资源,但所做的操作不一样;必须用到判断标记while(flag)、唤醒notifyAll()。
实例:
/*
* 资源类:
*/
class Resoure {
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set(String name){
while(flag)
//当前线程等待 t1,t2
try {wait();} catch (Exception e) {}
this.name=name+"----"+count++;
System.out.println(Thread.currentThread().getName()+"......生产者....."+this.name);
flag=true;
//唤醒在此对象监视器上等待的所有线程
this.notifyAll();
}
public synchronized void out(){
while(!flag)
//当前线程等待 t3,t4
try {wait();} catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag=false;
//唤醒在此对象监视器上等待的所有线程
this.notifyAll();
}
}
12.如何让线程停止?
只有一种,让run方法结束,只要控制住循环就能让其结束。
特殊情况:当线程处于了冻结状态,就不会读取该标记,线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,
这时候要对冻结进行清除,这样就可以操作标记让线程结束。
Thread类提供interrupt();
如果线程在调用 de>Objectde> 类的 de>wait()de>、de>wait(long)de> 或 de>wait(long, int)de> 方法,或者该类的 de>join()de>、de>join(long)de>、de>join(long, int)de>、de>sleep(long)de> 或 de>sleep(long, int)de> 方法过程中受阻,则其中断状态将被清除,它还将收到一个 de>InterruptedExceptionde>。
例如:
public class StopThread implements Runnable {
private boolean flag = true;
public synchronized void run() {
while (flag) {
try {
//线程处于冻结状态下,不会读取标记,线程不会结束。
wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()
+ "...........exception");
flag = false;
}
System.out.println(Thread.currentThread().getName() + ".....run");
}
}
public void chagerFlag() {
flag = false;
}
public static void main(String[] arg){
StopThread s = new StopThread();
Thread t1= new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
int num=0;
while(true){
if(num++==60){
s.chagerFlag();
t1.interrupt();
//中断该线程,将其中断状态清除。
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+":"+num);
}
System.out.println("over");
}
}
思路如下:
开始t1执行后,生产一个商品,flag为true,1放弃资格,t2判断flag为true,t2放弃资格。t3开始执行,判断flag为true,消费一个商品,flag为false, t4判断flag为false, t4放弃资格
当t1获得执行权后,执行完后flag为true,生产一个商品,唤醒了t2 。t1放弃资格,t2就直接往下执行了,然后又输出一个“生产者”,所以出现了生产两个商品的情况。
解决的方法就是将if改为while结构后,t2就不会再生产了,直接放弃资格,最后导致全部等待,所以要把this.notify()改为this.notifyAll()就可以解决了。
13.join():当线程执行到了B线程的join方法时,A就会等待;等B执行完了,A才执行;可以用来临时加入线程执行!