线程安全问题产生的原因:
1.多个线程在操作共享的数据。
2.操作共享数据的线程代码有多条。
当一个线程在指向操作共享数据的多条代码过程中,其他线程参与了运算,
就会导致线程安全问题。
解决思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,
其他线程不可以参与运算。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
线程同步
Java多线程支持引入同步监视器来解决多个线程同时操作一个文件的问题,使用同步监视器的通用方法就是同步代码块。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
synchronized后括号中的obj就是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时候只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放对该同步监视器的锁定。
虽然Java程序允许使用任何对象来作为同步监视器,同步监视器的目的是为了阻止多条线程同时对一个共享资源进行并发访问。因此通常推荐使用可能被并发访问的共享资源充当同步监视器。
同步方法
与同步代码块对应的,Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字修饰某个方法。则该方法称为同步方法。对于同步方法而言,无须显示指定同步监视器,同步方法的同步监视器是this,也就是该对象本身。
格式:public synchronized void methodName(){
同步代码块
}
同步的好处和弊端。
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了程序的效率,因为同步外的线程都会判断同步锁。
同步的前提:
同步中必须有多个线程,并使用同一个锁。
同步函数的锁是this.
同步锁(Lock)
Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock实现允许更灵活的结构,可以具有差别很大的属性,并且可以支持多个相关的Condition对象。
Lock是控制多个线程对共享资源进行访问的工具,通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。不过,某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁)。在实现线程安全的控制中,通常使用ReentrantLock(可重入锁)。使用该Lock对象可以显示的加锁和释放锁。
Lock对象的代码格式:
public class LockTest {
// 定义锁对象
private final ReentrantLock lock = new ReentrantLock();
public void method() {
// 加锁
lock.lock();
try {
// 需要保证线程安全的代码
} catch (Exception e) {
// TODO: handle exception
} finally {
lock.unlock();
}
}
}
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
*********建议使用同步代码块***********。
静态的同步函数使用的锁是:该函数所属的字节码文件对象。
可以用getClass获取,也可以用 当前类名.class表示。
例如:Class clazz1 = t.getClass();
Class clazz2 = Ticket2.class;
-----------------------------------------------------------------------------------------
/**
* 饿汉式
* @author TnTSuper
*
*/
/*class Single{
private Single(){};
private static final Single instance=new Single();
public static Single getInstance(){
return instance;
}
}*/
/**
* 懒汉式
*/
class Single{
private Single(){};
private static Single s=null;
/* //同步方法比较低效率
public static synchronized Single getInstance(){
if(s==null){
s=new Single();
}
return s;
}*/
/**高效的同步方法的饿汉式
1.静态的同步函数使用的锁是:该函数所属的字节码文件对象。
2.懒汉式可以延迟加载
3.多线程访问会出现安全问题,需采用同步代码块!
*/
public static Single getInstance(){
if(s==null){
synchronized (Single.class) {
if(s==null){
s=new Single();
}
}
}
return s;
}
}
--------------------------------------------------------------------------
死锁:常见情景之一:同步的嵌套。同步中还有同步。(面试中可能会出现)
当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
/**
* 死锁的一个例子!
* @author TnTSuper
*
*/
class Test111 implements Runnable{
private boolean flag;
public Test111(boolean flag){
this.flag=flag;
}
public void run(){
if(flag){
synchronized (MyLock.myLock_A) {
System.out.println("if....myLock_A");
synchronized (MyLock.myLock_B) {
System.out.println("if....myLock_B");
}
}
}else {
synchronized (MyLock.myLock_B) {
System.out.println("else....myLock_B");
synchronized (MyLock.myLock_A) {
System.out.println("else....myLock_A");
}
}
}
}
}
class MyLock{
static Object myLock_B=new Object();
static Object myLock_A=new Object();
}
public class DeadLockTest {
public static void main(String[] args){
Thread t1=new Thread(new Test111(true));
Thread t2=new Thread(new Test111(false));
t1.start();
t2.start();
}
}
----------------------------------------------------------------------------------
线程间通讯:
多个线程在处理同一资源,但是任务却不同。
线程通信
线程协调运行
为了让线程协调运行,借助Object类提供的wait()、notify()和notifAll()三个方法。这三个方法不属于Thread类,而属于Object类。但这三个方法必须由监视器对象来调用。
(1)对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法。
(2)对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这三个方法。
Object类提供的三个方法解释:
(1)wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。
(2)notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
(3)notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可能执行被唤醒的线程。
多生产者多消费者问题。
if判断标记只有一次,会导致不该运行的线程运行了,会出现数据错误的情况。
while判断标记,解决了线程获取执行权后,是否要运行。
notify:只能唤醒一个线程,如果唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。
notifyAll 解决了本方线程一定会唤醒对方线程的问题。
public class ProducerConsumerDemo {
/**
* 对于多个生产者和消费者。 为什么要定义while判断标记。 原因:让被唤醒的线程再一次判断标记。
*
* 为什么定义notifyAll, 因为需要唤醒对方线程。 因为只用notify,容易出现只唤醒本方线程的情况。 导致程序中的所有线程都等待。
*/
public static void main(String[] args) {
Resource r = new Resource();
new Thread(new Producer(r)).start();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();
new Thread(new Consumer(r)).start();
}
}
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "生产者" + this.name);
this.name = name + "--" + count++;
flag = true;
this.notifyAll();
}
public synchronized void Out() {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+ "--------消费者--------" + this.name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable {
Resource resource;
public Producer(Resource resource) {
this.resource = resource;
}
public void run() {
while (true) {
resource.set("+商品+");
}
}
}
class Consumer implements Runnable {
Resource resource;
public Consumer(Resource resource) {
this.resource = resource;
}
public void run() {
while (true) {
resource.Out();
}
}
}
-----------------------------------------------------------------
JDK1.5解决办法:
jdk1.5以后将同步和锁封装成了对象。
并将操作锁的隐式方式定义到了该对象中,将隐式的动作变成了显示动作。
Lock接口:出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成了显示锁操作。
同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁
unlock():释放锁,通常需要定义在finally代码块中。
Condition接口:出现替代了object中的wait notify notifyAll方法。
将这些监视器方法单独进行了封装,变成Condition监视器对象。
可以和任意的锁进行组合。
await();
signal();
signalAll();
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* JDK1.5 中提供了多线程升级解决方案。 将同步Synchronized替换成现实Lock操作。
* 将Object中的wait,notifynotifyAll,替换了Condition对象。 该对象可以Lock锁 进行获取。
* 该示例中,实现了本方只唤醒对方操作。
*
* Lock:替代了Synchronized lock unlock newCondition()
*
* Condition:替代了Object wait notify notifyAll await(); signal(); signalAll();
*/
public class ProducerConsumerDemo2 {
/**
* 对于多个生产者和消费者。 为什么要定义while判断标记。 原因:让被唤醒的线程再一次判断标记。
*
* 为什么定义notifyAll, 因为需要唤醒对方线程。 因为只用notify,容易出现只唤醒本方线程的情况。 导致程序中的所有线程都等待。
*/
public static void main(String[] args) {
Resource2 r = new Resource2();
new Thread(new Producer2(r)).start();
new Thread(new Producer2(r)).start();
new Thread(new Consumer2(r)).start();
new Thread(new Consumer2(r)).start();
}
}
class Resource2 {
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
Condition conn_pro = lock.newCondition();
Condition conn_cons = lock.newCondition();
public void set(String name) throws InterruptedException {
lock.lock();
try {
while (flag) {
try {
conn_pro.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "生产者"
+ this.name);
this.name = name + "--" + count++;
flag = true;
conn_cons.signal();
} finally {
lock.unlock();
}
}
public synchronized void Out() throws InterruptedException {
lock.lock();
try {
while (!flag) {
try {
conn_cons.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ "--------消费者--------" + this.name);
flag = false;
conn_pro.signal();
} finally {
lock.unlock();
}
}
}
class Producer2 implements Runnable {
Resource2 resource;
public Producer2(Resource2 resource) {
this.resource = resource;
}
public void run() {
while (true) {
try {
resource.set("+商品+");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer2 implements Runnable {
Resource2 resource;
public Consumer2(Resource2 resource) {
this.resource = resource;
}
public void run() {
while (true) {
try {
resource.Out();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
-------------------------------------------------------------------------------
多线程中的一点小细节:
怎么控制线程的任务结束呢?
任务中都会有循环结构,只要控制住循环就可以结束任务。
控制循环通常就用定义标记来完成。
但是如果线程处于冻结状态,无法读取标记,如何结束呢?
可以使用interrupt();方法将线程从冻结状态强制恢复到运行状态中来,
让线程具备cpu的执行资格。
但是强制动作会发生InterruptedException异常,记得要处理。
setDaemon()当正在运行的线程都是守护线程时,虚拟机退出。
如果所有的前台线程都结束,后台线程无论处于何种状态都会自动结束。
toString()线程的字符串表现形式。
线程优先级在1到10之间10个数。
优先级越大被执行到的几率越大。
t1.setPriority(Thread.MAX_PRIORITY);设置线程t1的优先级为10.
默认优先级是5
线程组:ThreadGroup
yield()方法 可以使线程暂停,暂时释放执行权。
-----------------------------------------------------------------------------------
/**
* join: 当A线程执行到了B线程的.join()方法时,A就会等待。
* 等B线程都执行完,A才会执行。 join可以用来临时加入线程执行。
*/
class Demo implements Runnable {
public void run() {
for (int x = 0; x < 70; x++) {
System.out.println(Thread.currentThread().getName().toString() + "..." + x);
//使用频率不高,面试
Thread.yield();
}
}
}
public class JoinDemo {
public static void main(String[] args) throws Exception {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
// t1.join();
t2.start();
//t1.join();
for (int x = 0; x < 80; x++) {
System.out.println("main========" + x);
}
System.out.println("End");
}
}