Java学习06-线程1




1.进程概述
  • 进程:是一个正在执行中的程序。
           每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
  • 线程:就是进程中的一个独立的控制单元。
          线程在控制着进程的执行。
          一个进程中至少有一个线程。
          Java VM  启动的时候会有一个进程java.exe.
          该进程中至少一个线程负责java程序的执行。
          而且这个线程运行的代码存在于main方法中。
          该线程称之为主线程。
  • 扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。



2.如何在自定义的代码中,自定义一个线程呢?
步骤:
  • 定义类继承Thread。
  • 复写Thread类中的run方法。
  • 目的:将自定义代码存储在run方法。让线程运行。

3,调用线程的start方法,

 该方法两个作用:启动线程,调用run方法。
 发现运行结果每一次都不同。
因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。



4.为什么要覆盖run方法呢?


Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程要运行的代码。
代码:线程的基本构建方法。

5.通过继承Runnable来创建线程。

需求:简单的卖票程序。

多个窗口同时买票。

创建线程的第二种方式:实现Runable接口

步骤:

  • 定义类实现Runnable接口
  • 覆盖Runnable接口中的run方法。
  • 将线程要运行的代码存放在该run方法中。
  • 通过Thread类建立线程对象。 

将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

为什么要将Runnable接口的子类对象传递给Thread的构造函数。

因为,自定义的run方法所属的对象是Runnable接口的子类对象。

所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。

调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。 

现方式和继承方式有什么区别呢? 

实现方式好处:避免了单继承的局限性。

在定义线程时,建立使用实现方式。

两种方式区别:

继承Thread:线程代码存放Thread子类run方法中。

实现Runnable,线程代码存在接口的子类的run方法。


package nuddles.j2seDemo;

public class ThreadDemo {

	 * @param args
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new SubThread().start();
		new SubThread().start();
		new Thread(new SubThread2()).start();
//		实现接口来创新线程
		new Thread(new SubThread2()).start();
	}

}

class SubThread extends Thread{
//	继承Thread来开启多线程
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		for (int i = 0; i <50; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

class SubThread2 implements Runnable{
//	实现接口创建多线程
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i <50; i++) {
			System.out.println(Thread.currentThread().getName()+"........:"+i);
		}
	}
}


6.练习:

创建两个线程,和主线程交替运行。

原来线程都有自己默认的名称。

Thread-编号 该编号从0开始。

static Thread currentThread():获取当前线程对象。

getName(): 获取线程名称。

设置线程名称:setName或者构造函数。


class Thread1 extends Thread {
         Thread1(String name){
                   super(name);
         }
         public void run(){
                   for (int i = 0; i < 60; i++) {
                            System.out.println(Thread.currentThread().getName());//Thread.currentThread()获取当前线程
                   }
                  
         }
}
 
public class ThreadTest{
         public static void main(String[] args) {
                   Thread1 line1 =new Thread1("line1");
                   Thread1 line2 =new Thread1("line2");
                   line1.start();
                   line2.start();
         }
}

7.多线程同时操作一个数据时的安全问题

问题的原因:

 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,

 另一个线程参与进来执行。导致共享数据的错误。

解决办法:

 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式。

就是同步代码块。

synchronized(对象)

{

 需要被同步的代码

}

对象如同锁。持有锁的线程可以在同步中执行。

没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

火车上的卫生间---经典。

同步的前提:

  • 必须要有两个或者两个以上的线程。
  • 必须是多个线程使用同一个锁。
  • 必须保证同步中只能有一个线程在运行。

好处:解决了多线程的安全问题。

弊端:多个线程需要判断锁,较为消耗资源,

Synchronized(this)锁当前对象,两个不同线程持有同一个this执行会锁掉一个


class  Ticket implements Runnable {
	static int ticket = 2000;
	Object obj = new Object();
	public  void run(){
		while (true) {
			synchronized(obj){
				// obj作为锁的对象
				if (ticket >0) {
					try{Thread.sleep(10);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"sale ticket"+ticket--);
				}
			
			}
		}
	}
}

public class TicketTest{
	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();
	}
}

8.安全问题的另一种解决方案:同步代码块
格式:在 函数上加上 synchronized  修饰符即可。
同步函数和同步代码块的区别:
1、同步函数的锁是固定的 this
2、同步代码块的锁是任意的对象。
建议使用同步代码块。
由于同步函数的锁是固定的 this, 同步代码块的锁是任意的对象, 那么如果同步函数和
同步代码块都使用 this 作为锁,就可以实现同步。

静态的同步函数使用的锁是 该函数所属字节码文件对象 ,可以用 getClass 方法获取,
也可以用当前类名.class 表示。


练习:
/*
需求:
银行有一个金库。
有三个储户分别存300员,每次存100,存3次。
目的:该程序是否有安全问题,如果有,如何解决?
如何找问题:
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。

*/
class Bank {
	private int sum;
	public synchronized void add(int n){
		// 先在bank中定义存钱的方法
		try{Thread.sleep(10);}catch(Exception e){}
		// 睡一会
		sum  = sum+n;
		System.out.println("sum="+sum);
	}
}

class Cus implements Runnable{
	private Bank bank = new Bank();
	public void run(){
		for (int i = 0; i<3; i++) {
			bank.add(100);
			// 调用三次,上锁
		}
	}
}

public class BankTest{
	public static void main(String[] args) {
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		Thread t3 = new Thread(c);
		t1.start();
		t2.start();
		t3.start();
	}
}

9.死锁问题:
同步同嵌套同步,容易引起死锁
如下例:t1线程拿到了a锁,想进b锁,
               t1线程拿到了b锁,想进a锁,

package nuddles.j2seDemo;

public class ThreadDeadDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Thread(new SubThread(false)).start();
		new Thread(new SubThread(true)).start();
//		先有自己的锁然后都想拿对方的锁,都抢不到,卡死了
	}

}

class Lock{
	public  static Object  locka = new Object();
	public  static Object  lockb = new Object();
}

class SubThread implements Runnable{
	boolean flag = false;
	
	public SubThread(boolean flag) {
		super();
		this.flag = flag;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			if (flag) {
//				先拿a锁,再拿b锁
				synchronized (Lock.locka) {
					System.out.println(Thread.currentThread().getName()+".....locka");
					synchronized (Lock.lockb) {
						System.out.println(Thread.currentThread().getName()+".....lockb");
					}
				}
			}else{
				synchronized (Lock.lockb) {
//					先拿锁,再拿锁
					System.out.println(Thread.currentThread().getName()+".....lockb");
					synchronized (Lock.locka) {
						System.out.println(Thread.currentThread().getName()+".....locka");
					}
			
				}
			}
		}
	}
}
Thread-1.....locka
Thread-0.....lockb

10.线程间通讯:

多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
①wait():让线程处于冻结状态,被 wait 的线程会被存储到线程池中。
②notify():唤醒线程池中的一个线程(任何一个都有可能)。
③notifyAll():唤醒线程池中的所有线程。
P.S.
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、 wait 和 sleep 区别?
①wait 可以指定时间也可以不指定。 sleep 必须指定时间。
②在同步中时,对 CPU 的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法 wait、 notify、 notifyAll 定义在了 object 类中,因为这些方法
是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方式一定在 object 类中。



/*编写程序,实现数据库的同步读写数据*/
package nuddles.j2seDemo;

public class DateBaseDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		DataBase  database = new DataBase();
		new Thread(new Input(database)).start();
		new Thread(new Output(database)).start();
	}

}

class DataBase{
	String name;
	String sex;
	boolean flag;
	public  synchronized void set(String name,String sex){
//		将设值的方法定义在方法中
		if (flag) {
			try{this.wait();}catch(InterruptedException e ){}
//			如果真说明已经写入过数据了,那么应该等着
		}else {
			this.name = name;
			this.sex = sex;
			try{this.notify();}catch(Exception e ){}
//			没有就写入,然后叫醒out来输出
			this.flag = true;
		}
	}
	public synchronized void out(){
        if(!flag){
                 try{this.wait();}catch(Exception e){}
//                 如果没有数据,那么不读,等着
        }
        System.out.println(name+"....."+sex);
        flag = false;
        this.notify();
//        如果有数据读出来,读完后叫input赶紧来写
	}
}

class Input implements Runnable{
	private DataBase db;
    Input(DataBase db){
              this.db = db;
    }
    public void run(){
              int i =0;
              for (int j=0; j<20;j++ ) {
	                if (i==0) {
	                          db.set("nuddlws","man");
	                }else{
	                          db.set("张三","女");      
	                }
	                i =(i+1)%2;
//	                记住此方法用于轮转
              }                
   }
}

class Output implements Runnable{
	private DataBase db;

	public Output(DataBase db) {
		super();
		this.db = db;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 20; i++) {
			db.out();
		}
	}
}

11.生产者消费者代码:
对于多个生产者和消费者。
为什么要定义while判断标记。
原因: 让被唤醒的线程再一次判断标记。


为什么定义 notifyAll
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
package nuddles.j2seDemo;

public class ConsumerProducer {
	public static void main(String[] args) {
		Kaoya kaoya = new Kaoya();
		new Thread(new Consumer(kaoya)).start();
		new Thread(new Consumer(kaoya)).start();
		new Thread(new Consumer(kaoya)).start();
		new Thread(new Producer(kaoya)).start();
		new Thread(new Producer(kaoya)).start();
		new Thread(new Producer(kaoya)).start();
	}
}

class Kaoya{
	int number;
	boolean flag;
	@Override
	public String toString() {
		return "Kaoyan [number=" + number + "]";
	}
	public synchronized void  produce() {
		while (flag) {
//			用while第次都回去判断
			try{this.wait();}catch(Exception e){}
		}
		number++;
		System.out.println(Thread.currentThread().getName()+"生产都生产+"+this.toString());
		this.flag = true;
		this.notifyAll();
//		叫醒所有线程
	}
	public synchronized void consumer(){
		while(!flag){
			try{this.wait();}catch(Exception e){}
		}
		System.out.println(Thread.currentThread().getName()+"消费都吃了....."+this.toString());
		this.flag = false;
		this.notifyAll();
	}
}


class Consumer implements Runnable{
	private Kaoya kaoya;
	
	public Consumer(Kaoya kaoya) {
		super();
		this.kaoya = kaoya;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
			kaoya.consumer();
		}
	}
}
class Producer implements Runnable{
	private Kaoya kaoya;
	
	public Producer(Kaoya kaoya) {
		super();
		this.kaoya = kaoya;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
			kaoya.produce();
		}
	}
}

12.jdk1.5新特性

同步代码块就是对于锁的操作是隐式的。
JDK1.5 以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将
隐式动作变成了显示动作。
Lock 接口: 出现替代了同步代码块或者同步函数, 将同步的隐式操作变成显示锁操作。
403
同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock(): 释放锁, 为了防止异常出现, 导致锁无法被关闭, 所以锁的关闭动作要放在
finally 中。
Condition 接口: 出现替代了 Object 中的 wait、 notify、 notifyAll 方法。 将这些监视
器方法单独进行了封装,变成 Condition 监视器对象,可以任意锁进行组合。
Condition 接口中的 await 方法对应于 Object 中的 wait 方法。
Condition 接口中的 signal 方法对应于 Object 中的 notify 方法。
Condition 接口中的 signalAll 方法对应于 Object 中的 notifyAll 方法。
使用一个 Lock、一个 Condition 修改上面的多生产者-多消费者问题:

改写上个代码

package nuddles.j2seDemo;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {

	 * @param args
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Kaoji kaoya = new Kaoji();
		new Thread(new Consumer1(kaoya)).start();
		new Thread(new Consumer1(kaoya)).start();
		new Thread(new Consumer1(kaoya)).start();
		new Thread(new Producer1(kaoya)).start();
		new Thread(new Producer1(kaoya)).start();
		new Thread(new Producer1(kaoya)).start();
	}

}

class Kaoji{
	int number;
	boolean flag;
	@Override
	public String toString() {
		return "Kaoyan [number=" + number + "]";
	}
	ReentrantLock lock = new ReentrantLock();
	Condition con = lock.newCondition();
//	定义锁和条件
	public void  produce() {
		lock.lock();
		try{
			while (flag) {
//			用while第次都回去判断
			try{con.await();}catch(Exception e){}
			}
			number++;
			System.out.println(Thread.currentThread().getName()+"生产都生产+"+this.toString());
			this.flag = true;
			con.signalAll();
	//		叫醒所有线程
		}finally{
			lock.unlock();
		}
		
	}
	public  void consumer(){
		lock.lock();
		try{
			while(!flag){
			try{con.await();}catch(Exception e){}
			}
			System.out.println(Thread.currentThread().getName()+"消费都吃了....."+this.toString());
			this.flag = false;
			con.signalAll();
		}finally{
			lock.unlock();
		}
		
		
	}
}


class Consumer1 implements Runnable{
	private Kaoji kaoya;
	
	public Consumer1(Kaoji kaoya) {
		super();
		this.kaoya = kaoya;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
			kaoya.consumer();
		}
	}
}
class Producer1 implements Runnable{
	private Kaoji kaoya;
	
	public Producer1(Kaoji kaoya) {
		super();
		this.kaoya = kaoya;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
			kaoya.produce();
		}
	}
}

13.如何停止线程?

只有一种,run方法结束。

开启多线程运行,运行代码通常是循环结构。

只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:

当线程处于了冻结状态。

就不会读取到标记。那么线程就不会结束。

当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。

强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。

Thread类提供该方法 interrupt();强制动作会发生 InterruptedException,一定要记得处理。

class Test implements Runnable {
    boolean flag = true;
    public synchronized void run(){
        while (flag) {
            try{this.wait();}catch(Exception e){}
            setFlag(false);
            System.out.println(Thread.currentThread().getName()+"over");
        }
    }
    public void setFlag(boolean flag){
        this.flag = flag;
    }
}
 
public class StopThread{
    public static void main(String[] args)throws Exception {
        Test r = new Test();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        for (int i = 0; i<5000;i++) {
            System.out.println(Thread.currentThread().getName()+"....running...");
 
            if (i == 3000) {
            r.setFlag(false);
            t1.interrupt();//强制复苏线程
            t2.interrupt();
            }  
        }           
        System.out.println(Thread.currentThread().getName()+"..over..");
    }
}

14.setdaemon(boolean)设置守护线程
必须在开始前设置。
如果其它线程全部结束,只剩下守护线程,则守护线程自动结束。

15.join:
当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。

16.优先级:

SetPriority(1-10)设置优先级。

Thread.MAX_PRIORITY 10

Thread.MIN_PRIORITY 1

Thread.NORM_PRIORITY 5

17yield方法:
暂停当前正在执行的线程对象,并执行其他线程。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值