线程同步与线程通道(锁)

※ 线程的同步

线程的职责就是执行一些操作,而多数操作都涉及到处理数据。这里有一个程序处理实例变量a:

            a+=i;
            a-=i;
            System.out.println(a);

多个线程在操纵共享资源——实例变量时,有可能引起共享资源的况争。为了保证每个线程能正常执行操作,保证共享资源能正常访问和修改。Java引入了同步进制,具体做法是在有可能引起共享资源竞争的代码前加上synchronized标记。这样的代码被称为同步代码块。

每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。当一个线程试图执行带有synchronized标记的代码块时,该线程必须首先获得this关键字引用的对象的锁。

. 如果这个锁已经被其他线程占用,Java虚拟机就会把这个线程放到this指定对象的锁池中,线程进入阻塞状态。在对象的锁池中可能会有许多等待锁的线程。等到其他线程释放了锁,Java虚拟机会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
. 假如这个锁没有被其他线程占用,线程就会获得这把锁,开始执行同步代码块。在一般情况下,线程只有执行完同步代码块,才会释放锁,使得其他线程能够获得锁。

如果一个方法中的所有代码都属于同步代码,则可以直接在方法前用synchronized修饰(普通方法加锁,锁对象是this)。

     public synchronized String pop(){...}
      等价于
            public String pop(){
                   synchronized(this){...}
            }

※ 线程同步的特征:

1.如果一个同步代码块和非同步代码块同时操纵共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他线程仍然可以执行对象的非同步代码块。
2.每个对象都有唯一的同步锁。
3.在静态方法前面也可以使用synchronized修饰符。此时该同步锁的对象为类对象(类的Class对象)。
      4.当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行。进入同步代码块的线程也可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并没有释放锁,只是把运行机会(即CPU)让给了其他的线程。
5.synchnozied声明不会被继承。
同步是解决共享资源竞争的有效手段。当一个线程已经在操纵共享资源时,其他共享线程只能等待。为了提升并发性能,应该使同步代码块中包含尽可能少的操作,使得一个线程能尽快释放锁,减少其他线程等待锁的时间。

※ 线程同步与异步区别:
线程同步:
多个线程执行的时候,排队执行,一个线程结束另一个线程才执行
synchronized是加锁的操作,对代码块或者方法加锁,获得锁的线程才可以执行。

线程异步:
多个线程自动抢夺cpu的执行权限,谁抢 上谁执行(交替执行)

package com.briup.Thread;
/*
 * 造成线程不安全的原因,多个线程
 * 共享一个数据(变量。对象)
 */
public class NumberThread1 {
	public static void main(String[] args) {
		Runnable run=new Number1();
		Runnable run1=new Number1();
		Thread t=new Thread(run,"1号");
		Thread t1=new Thread(run1,"2号");
		t.start();
		t1.start();
	}
}
class Number1 implements Runnable{
	private int num;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(;num<20;num++){
			System.out.println(Thread.currentThread().getName()+":"+num);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}


package com.briup.Thread;

public class NumberThread {
		public static void main(String[] args) {
			Thread t1=new NumberT("test");
			Thread t2=new NumberT("test1");
			t1.start();
			t2.start();
		}
}
class NumberT extends Thread{
	//private int num;
	//静态的变量是属于类的,被所有对象共享
	//所以会造成线程不安全的问题
	private static int num;
	public NumberT(String name) {
		super(name);
	}
	@Override
	public void run() {
		for(;num<20;num++){
			System.out.println(getName()+":"+num);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

※ 线程的通信

1)锁对象.wait(): 执行该方法的线程释放对象的锁,Java虚拟机把该线程放到该对象的等待池中。该线程等待其它线程将它唤醒;
2)锁对象.notify(): 执行该方法的线程唤醒在对象的等待池中等待的一个线程。Java虚拟机从对象的等待池中随机选择一个线程,把它转到对象的锁池中。如果对象的等待池中没有任何线程,那么notify()方法什么也不做。
3)锁对象.notifyAll():会把对象的等待池中的所有线程都转到对象的锁池中。
注意:notify notifyAll只会唤醒等待池中等待同一个锁对象的线程,因为同一个时刻在等到池中可能会有多个线程,而这多个线程可能是在等待不同的锁对象

假如t1线程和t2线程共同操纵一个s对象,这两个线程可以通过s对象的wait()和notify()方法来进行通信。通信流程如下:

1.当t1线程执行对象s的一个同步代码块时,t1线程持有对象s的锁,t2线程在对象s的锁池中等待;
2.t1线程在同步代码块中执行s.wait()方法, t1释放对象s的锁,进入对象s的等待池;
3.在对象s的锁池中等待锁的t2线程获得了对象s的锁,执行对象s的另一个同步代码块;
4.t2线程在同步代码块中执行s.notify()方法,Java虚拟机把t1线程从对象s的等待池移到对象s的锁池中,在那里等待
获得锁。
5.t2线程执行完同步代码块,释放锁。t1线程获得锁,继续执行同步代码块。

package com.briup.Thread;

public class synchronizedThread1 {
	public static void main(String[] args) {
		Thread t=new MyThread1();
		Thread t1=new MyThread1();
		t.start();
		t1.start();
	}
}
class MyThread1 extends Thread{
	public static int num;
	@Override
	public void run() {
		//保证对共享的数据操作的时候线程安全()
		//给操作的所有代码加锁
		//参数是锁对象,可以是任意对象,
		//能不能锁住代码?
		//一旦线程获取到锁对象,要等到锁中代码执行完成
		//才会释放锁
		//	this代表了线程对象各自本身,在执行代码的时候
		//各自有各自的锁,锁不住
		//要锁住代码,锁必须是唯一
		synchronized (this) {
			for(;num<20;num++){
				System.out.println(getName()+":"+num);
				if(num==10){
					num++;
					//释放锁,break结束for循环
					break;
				}
			}
		}
	}
}


package com.briup.Thread;

public class synchronizedThread4 {
	public static void main(String[] args) {
		Runnable run=new MyThread4();
		Runnable run1=new MyThread4();
		Thread t=new Thread(run,"1号");
		Thread t1=new Thread(run,"2号");
		t1.start();
		t.start();
		
	}
}
class MyThread4 implements Runnable{
	public static int num;
	//非静态方法加锁,锁对象是this
	@Override
	public synchronized void run() {
			for (; num < 20; num++) {
				System.out.println(Thread.currentThread().getName() + ":" + num);
				try {
					//线程一旦获取到锁对象之后,即时进入阻塞状态(sleep())
					//也不会释放锁
					//锁的释放只能是代码块执行完成
					//或者线程自动放弃锁对象(wait())
					//一旦调用wait线程会进入等待池,进入等待
					//池的线程必须被唤醒才会去重新抢夺锁
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
	}
	
}



package com.briup.Thread;

public class synchronizedThread5 {
	public static void main(String[] args) {
		Runnable run=new MyThread5();
		Thread t=new Thread(run,"1号");
		Thread t1=new Thread(run,"2号");
		t1.start();
		t.start();
		
	}
}
class MyThread5 implements Runnable{
	public static int num;
	//非静态方法加锁,锁对象是this
	@Override
	public  void run() {
			test();
	}
	//静态方法加锁,锁对象是单前静态方法
	//所属的类的镜像 MyThread5.class
	public synchronized static void test(){
		for (; num < 20; num++) {
			System.out.println(Thread.currentThread().getName() + ":" + num);
		}
	}
}

※ wait,notify,notifyAll方法

package com.briup.Thread;

public class synchronizedThread6 {
	public static void main(String[] args) {
		Thread t=new MyThread6("1号");
		Thread t1=new MyThread6("2号");
		t1.start();
		t.start();
		
	}
}
class MyThread6 extends Thread{
	public static int num;
	public MyThread6(String name) {
		super(name);
	}
	@Override
	public void run() {
		synchronized (MyThread6.class) {
			for(;num<20;num++){
				System.out.println(getName()+":"+num);
				if(num==10){
					num++;
					//wait() notify() notifyAll()必须由锁对象调用
					try {
						MyThread6.class.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			//通知等待池中的线程(和单前线程拥有相同锁对象)
		   //notify()随机通知一个,通知完后进入锁池等待,
			//等待锁对象,一旦有其他线程放开锁,等待的线程
			//就会获取锁进入就绪状态
			MyThread6.class.notify();
		}
	}
}



package com.briup.Thread;

public class synchronizedThread7 {
	public static void main(String[] args) {
		Thread t=new MyThread7("1号");
		Thread t1=new MyThread7("2号");
		Thread t2=new MyThread7("3号");
		t1.start();
		t.start();
		t2.start();
		
	}
}
class MyThread7 extends Thread{
	public static int num=1;
	public int count;
	public static Object obj=new Object();
	public MyThread7(String name) {
		super(name);
	}
	@Override
	public void run() {
		synchronized (obj) {
			for (; num <= 18;) {
				System.out.println(getName() + ":" + num++);
				count++;
				if(count==3){
					count=0;
					try {
						obj.notify();
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			obj.notifyAll();
		}
	}
}

※ 线程的死锁

假设有两个线程,都需要两把锁a,b. A线程持有a,等待B线程持有的锁b,而B线程持有b,正在等待A持有的锁a;

※ 线程让步

Thread.yield()静态方法,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。

sleep()和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。区别:

1)sleep()不考虑其他线程优先级;yield()只会给相同优先级或者更高优先级的线程一个运行的机会。
2)sleep()转到阻塞状态;yield()转到就绪状态;
3)sleep()会抛出InterruptedException异常,yield()不抛任何异常;
4)sleep()比yield方法具有更好的可移植性。对于大多数程序员说,yield()方法的唯一用途是在测试期间人为地提高程序的并发性能,以帮助发现一些隐藏的错误,所以yield()并不常用。

※ 调整线程优先级

注意:优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果。

线程优先级的使用原则与操作系统有着密切的联系因此在JAVA中的线程的调度是完全受其所运行平台的操作系统的线程调度程序控制的。所有虽然我们可以设置线程的优先级但是在运行的时候不一定能够确切的体现出来。
所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的线程获得较多的运行机会。Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。优先级用整数来表示,取值范围是1-10,Thread类有以下3个静态常量。

            . MAX_PRIORITY: 10, 最高;
            . MIN_PRIORITY: 1, 最低;
            . NORM_PRIORITY: 5, 默认优先级;

        其它:stop(): 中止线程运行;          已过时
              resume(): 使暂停线程恢复运行   已过时
              suspend(): 暂停线程,不释放锁; 已过时

※ 释放对象的锁:

a)执行完同步代码块;
b)执行同步代码块过程中,遇到异常而导致线程终止,释放锁;
c)执行同步代码块过程中,执行了锁所属对象的wait()方法,释放锁进入对象的等待池;

※ 线程不释放锁:

a)Thread.sleep()方法,放弃CPU,进入阻塞状态;
b)Thread.yield()方法,放弃CPU,进入就绪状态;
c)suspend()方法,暂停当前线程,已过时;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值