使用线程同步的原因:
解决多个线程操作同一个资源所产生的并发问题。
出现问题的代码:
class Ticket implements Runnable
{
private int num = 100;
Object obj = new Object();
public void run() {
while (true) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+".....sale...." + num--);
}
}
}
}
public class TicketDemo {
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();
}
}
打印结果:
打印的车票出现了0,-1,-2这些数字,原因就是在15行执行前,线程可能切换,然后下一次判断是小于0,但是当切换回来的时候,num已经小于0,所以出现不合理数据。
解决方法:
利用线程之间的同步来解决问题,要解决上面的问题,必须保证下面这段代码的"原子性
"。
对象锁
--必须有一个对象
Object object = new Object();
synchronized(object){
//被同步代码块
}
--锁字节码文件
synchronized(int.class){
//被同步的代码块
}
--this锁
synchronized(this){
//被同步的代码块
}
实现Runnable接口加锁
public class TestBank01 {
public static void main(String[] args) {
BankThread bt = new BankThread();
new Thread(bt, "A").start();
new Thread(bt, "B").start();
}
}
class BankThread implements Runnable {
private int money = 200000;
private Object object = new Object();
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "排队 谢谢合作");
// 对象锁 此处应该同步
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "取钱中");
money -= 20000;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "取了20000, 还剩下:" + money);
}
}
}
继承Thread类加锁:
public class TestBank02 {
public static void main(String[] args) {
BankThread_ bt1 = new BankThread_("A");
BankThread_ bt2 = new BankThread_("B");
bt1.start();
bt2.start();
}
}
class BankThread_ extends Thread {
//此处应该用static修饰,static 修饰的成员属于类,这样才能共享money
private static int money = 20000;
public BankThread_(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "取钱");
//此处应该上一样的锁,不能用属于此类中的所有对象都拥有的属性上锁
synchronized (BankThread_.class) {
money -= 2000;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "取了2000, 还剩"+ money);
}
}
}
注意:继承Thread类上锁和变量共享。
死锁问题:
一旦有多个线程,且它们都要争用对多个锁的独立访问,那么就有可能发生死锁。
死锁代码:
public class TestDeadLock01 {
public static void main(String[] args) {
DeadLock01 dl = new DeadLock01();
new Thread(dl, "AAA").start();
new Thread(dl, "BBB").start();
}
}
class DeadLock01 implements Runnable {
private Object objA = new Object();
private Object objB = new Object();
@Override
public void run() {
String name = Thread.currentThread().getName(); //获取名称
if(name.equals("AAA")){
synchronized (objA) {
System.out.println(name +"获得objA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objB) {
System.out.println(name +"获得objB");
}
}
}else{
synchronized (objB) {
System.out.println(name +"获得objB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objA) {
System.out.println(name +"获得objA");
}
}
}
}
}
package com.mixm;
public class TestDeadLock02 {
public static void main(String[] args) {
new DeadLock02("AAA").start();
new DeadLock02("BBB").start();
}
}
class DeadLock02 extends Thread {
//注意两个锁,设置为static
private static Object objA = new Object();
private static Object objB = new Object();
public DeadLock02(String name){
super(name);
}
@Override
public void run() {
String name = Thread.currentThread().getName();
if(name.equals("AAA")){
synchronized (objA) {
System.out.println(name +"获得objA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objB) {
System.out.println(name +"获得objB");
}
}
}else{
synchronized (objB) {
System.out.println(name +"获得objB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objA) {
System.out.println(name +"获得objA");
}
}
}
}
}
ThreadLocal:
线程局部变量,功能: 为了每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本。
构造方法: public void ThreadLocal();
方法:
public void set(Object value): 将此线程局部变量的当前线程副本中的值设置为指定值。
public T get(): 返回此线程局部变量的当前线程副本的值
public void remove(): 移除此线程局部变量当前线程的值。如果此线程局部变量随后被当前线程读取,且这期间当前线程没有设置其值,则将调用其initValue()方法重新初始化其值。这将导致在当前线程多次调用initialValue方法。
protected T initalValue(): 返回此线程局部变量的当前线程的"初始值"。线程第一个调用get()方法访问变量时将调用此方法,单如果线程之前调用了set(T)方法,则不会对该线程再调用initalValue方法。通常,此方法对每个线程最多调用一次,但如果在调用get()之后又调用了remove(),则有可能再次调用此方法。(protected提供给子类重写)
package com.mixm;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class TestThreadLocal {
public static void main(String[] args) {
MyThreadLocal02 mt = new MyThreadLocal02();
new Thread(mt, "AAA").start();
new Thread(mt, "BBB").start();
new Thread(mt, "CCC").start();
new Thread(mt, "DDD").start();
}
}
class MyThreadLocal02 implements Runnable {
private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss SS");
private ThreadLocal<Date> date = new ThreadLocal<Date>();
private ThreadLocal<String> names = new ThreadLocal<String>() {
protected String initialValue() {
return Thread.currentThread().getName();
}
};
@Override
public void run() {
try {
// 这儿用的是随机睡眠,如果睡眠的时间是一致的话,看不出来效果
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e1) {
e1.printStackTrace();
}
date.set(new Date());
System.out.println(names.get() + " " + sdf.format(date.get()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(names.get() + " " + sdf.format(date.get()));
// 移除之后添加进来的值
// date.remove();
// System.out.println(names.get() + "*******" + sdf.format(date.get())); //这儿会抛异常
// 移除在 initialValue()已经初始化的值移除此线程局部变量当前线程的值。
//正常输出,输出的值是initialValue()中初始化的内容
names.remove();
System.out.println(names.get() + "-------" + sdf.format(date.get()));
}
}
ThreadLocal类和同步的区别:
ThreadLocal类不能替代同步的机制,两者面对的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是个例多个线程的数据共享,从根本上就不在多个线程线程之间共享资源(变量)。这样当然不需要对多个线程进行同步。
所以,如果你需要进行多个线程之间进行通信,则使用同步进制。
如果需要个例多个线程之间的共享冲突,可以使用ThreadLoacl。