1.多线程,同步锁的简单概要

这是一个简单的例子,简单说明了多线程上的一个简单的应用。

最近一直在学习java,记录自己的学习历程和总结,以便于将来的复习,同时也希望多多指点,共同学习,共同进步,接下来的时候。会定期进行更新,


1.线程的基本状态,一定要了解。

1.必须调用start() 方法。可以在构造函数的时候,直接将start()方法写在构造函数中。
2.线程经过stat 方法,使线程进入 可运行状态Runnable,究竟谁来执行,由线程调度器来决定,被选择的进入 正在运行状态,线程体运行完成之后,线程死掉。
   
3.主线程 JVM创建出来的,在主线程中调用我们写好的main方法,另外两个是 T1和T2线程。当java程序执行的时候,主线程在哪?主线程处于正在运行状态中。这样才能执行main中的方法。

  1. 当main方法调用结束之后,主线程进入死亡状态。我们也可以在main方法中编写其他的代码,看到三个线程交替执行,
  2. 主线程也就是一个线程,就是JVm创建的而已,真正重要的是 线程调度器。也就是一段代码而已。覆盖的子类不能将异常扩大。
  3. 线程里出现,未检查异常。导致线程死亡。
  4. 多个操作要同时执行的时候,必须使用多线程,
  5. 只要用户线程没有结束。则JVm不会主动关闭的。
  6. main是在主线程中来执行的。main就是主线程的目标对象,

4.守护线程  
  1.  线程创建的时候,就都是默认用户线程,可以把一个用户线程标记为守护线程。
  2.  标记守护线程的操作。一定要在启动线程之前标记。否则会出现无效的线程
  3.  dt.SetDeamon(true)
  4. JVM发现当虚拟机只有守护线程在运行的时候,JVm会强行的停止守护线程,越就是说 jVM不会让守护线程一致运行的。
  5. 守护线程仅仅是为了 衬托用户线程。只要了解就可以了。

5.线程的优先级
  1. 每个线程都有一个优先级(1~10级).优先级越高,被线程调度器选中的机会就越高。
  2. 获得线程的优先级:thread.getPriority()
  3. 在目标对象中获取正在执行的该目标的对象: Thread.currentThread( )
  4. 在启动线程之前,通过线程的实例方法setPriority()改变线程的优先级.改变优先级之前要在启动线程之前完成,否则会出现异常。
  5. 线程的优先级用默认的5级就可以了,千万不要改变.
  6. 暂停当前正在执行的线程对象(yied),该线程对象重新回到就绪队列。该线程有可能被调度器选中,即使这样的话,也是让其他线程更多的获的运行机会。
  7. .sleep(1000)睡眠时间。线程在睡觉时候 不会回到就绪队列,睡醒的时候才会回到就绪序列。
  8. 线程的sleep() yield方法都是静态方法。静态方法调用的时候和对象没有关系, 在哪个线程中调用了线程的sleep方法,哪个线程就会睡觉, 在main方法中,调用ti对象的sleep方法,但是和t1没有关系,在主线程中调用的sleep方法,所以是主线程睡觉。
  9. 线程的优先级 千万不要改,用默认的5级就可以了,实际开发中用默认优先级。
  10. 线程在sleep()期间是不会回到就绪队列的.

6.主线程睡眠时间
  1.  我们在主线程中调用了thread的jion()方法,直到另一个线程执行完毕,主线程才会开始执行。
  2. 在主线程中调用Thread.jion()方法。

7.如何正确的终止程序?
  1. 线程的stop()方法已经废弃了。
  2. 在控制线程结束的时候,在线程中设置boolean型的开关量,让线程的循环和开关量结合起来。

8.线程调度与控制之yield方法
  1. 线程的sleep方法和yield方法都是静态方法,静态方法调用的时候和对象无关。
  2. 线程的yield方法会让当前线程放弃CPU,但是该线程会马上回到就绪队列,有可能被调度器重新选中。
  3. 线程的yield方法会让其他线程获得更多的运行机会。

9.线程控制之join方法
  1. 我们在主线程中调用了t1线程的实例方法jion,即:t1.jion(),则主线程会被阻塞,直到t1线程运行完毕,主线程才会接触阻塞,重新回到就绪队列。
  2. 换句话说:只要t1线程没有运行结束,主线程就不会运行。

10.线程守护
  1. 创建好线程对象之后,通过setDaemon()方法把线程标记为守护线程。
  2. 当JVM发现只有守护线程运行的时候,JVM会主动的关闭守护线程,然后关闭JVM。
  3. 守护线程 对应的是 用户线程, 线程创建的时候默认是用户线程。
  4. 用户线程没有结束,JVM是不会关闭的。


11.线程同步(加锁)
  1. synchronized(this)中的this是当前对象中指向当前对象的引用,使用当前对象来当作锁的。什么东西可以当作锁呢?任何对象都可以当作锁,
  2. 锁默认有时候是开着呢 当线程进入锁的时候,锁就被锁上了,别的线程就不能访问这个同步块,只有当这个线程执行完这个同步块的时候,锁被打开。别的线程才能进来执行同步块。
  3. 注意,线程获得锁之后,进入同步块,即使执行了sleep活着yield,线程是不会放锁的。
  4. 注意不是线程的。不能直接调用线程对象,要获取正在执行的线程的对象。               thread.currentThread() 获取当前正在执行的线程的对象。
  5. synchronized可以锁住整个方法么?可以的。把synchronized放到方法的返回类型之前,synchronized当作修饰符来使用,专门加在方法中用来控制线程同步的。    言外之意就是:可以修饰方法,表示这个方法被锁。但是只能修饰方法!
  6. 何时需要线程同步?  只有当多个线程 同时修改 相同数据的时候,才需要给数据加锁。
  7. 线程同步不能滥用,因为线程同步是以牺牲速度作为而代价的。
  8. ArrayList  ,HashMap  StringBuilder  : 新集合 , 速度快, 非线程安全的(就是说方法没有加锁)
  9. Vector     ,Hashtable  StringBuffer  : 老集合 , 速度慢, 线程安全的(就是说 方法上加锁了)
  10. 多个线程同时访问同一个对象的不同方法,这才是实际开发的正常现象。
  11. 生产者和消费者模式:  ⚠️                                                                                                                     
  12. (1)注意:锁和铃铛必须是同一个对象,否则会 出现 无效的监视器异常,                                            
  13. (2)注意:线程必须获得锁之后(必须进入同步块),才能调用obj.wait(),否则会出现异常;     
  14. (3)注意:生产者的次数和消费者的次数必须要一致;                                                                                
  15. (4)注意:在数据类中设置状态变量,让状态变量形成开关的状态;

下面贴出一个代码:可以从中理解线程同步的基本注意事项和实现方法:


 用数组实现一个后进先出的栈类


   栈类中出现的问题

   1.消费者取出null的问题

   2.消费者遇到-1的数组索引越界异常

   3.生产者遇到100的数组索引越界异常 

 

 解决方案:

1.给push()/pop()方法上面同时加上synchronized修饰符.这样的话,可以解决上述的第一个问题

但是对于第二个问题,第三个问题,通过加锁依旧是无法解决的

如何解决第二个/第三个问题呢? 采用生产者消费者模式来解决问题


生产者消费者模式的注意事项:

1.锁和铃铛必须是同一个对象才可以,否则会出现java.lang.IllegalMonitorStateException.

2.线程必须要获得锁之后(也就是进入到同步块中之后),才能调用obj.wait()/notify()方法

否则依旧会出现java.lang.IllegalMonitorStateException

3.生产的次数和消费的次数必须要一致   

4.在数据类中设置状态变量,让状态变量形成开关的状态 


public class Stack 

{

private String arr[] = new String[100];

private int index = -1;  // 栈的索引

private boolean dataIsReady = false// 开关控制变量,就是数据有没有准备好

/**

*  压栈的方法

* @param str

*/

public void push(String str)

{

synchronized (this)

{

// 当没有数据(dataIsReady为false)的时候,生产者线程应该生产数据

// 当有数据(dataIsReady为true)的时候,生产者线程应该阻塞

if(dataIsReady)

{

try 

{

// 线程获得锁之后进入同步块,如果执行了obj.wait(),则线程必须放锁,进入到阻塞状态

// 进入阻塞状态且放锁了,那么另一个线程就可以进入同步块开始执行了/

this.wait();

catch (InterruptedException e

{

e.printStackTrace();

}

}

// 因为索引是-1 所以要先+1.把索引上调

index++;

// 线程获得锁之后进入同步块,即使执行了sleep和yield方法,线程也不会放锁

//  就是说仍然在同步块中

// Thread.yield();  // 如果在同步块中,依然不能放锁。

arr[index] = str;

dataIsReady = true // 表示生产者已经生产数据了

this.notifyAll();  // 因为生产完数据了,所以摇铃通知消费者(弹栈者)

}

}

/**

*  弹栈的方法

* @return

*/

public String pop()

{

synchronized (this)

{

try 

{

// 当没有数据(dataIsReady为false)的时候,消费者线程应该阻塞

// 当有数据的时候(dataIsReady为true),消费者线程应该弹栈

if(dataIsReady == false)

{

this.wait();

}

catch (InterruptedException e

{

e.printStackTrace();

}

// 弹栈是从上往下开始弹栈的

String str = arr[index];

arr[index] = null;

index --;

// 改变开关变量

dataIsReady = false;

// 摇铃进行通知 生产者

this.notifyAll();

return str;

}

}

}




public class ShenChanZheThread extends Thread 

{

private Stack stack;

public ShenChanZheThread(String name,Stack stack)

{

super(name);

this.stack = stack;

start();

}

@Override

public void run()

{

for(int i=1;i<=200;i++)

{

// 生产者进行压栈

String str = "Hello- "+ i;

stack.push(str);

System.out.println(this.getName()+"第"+i+"次压栈成功,数据是:"+str);

}

}

}



public class XiaoFeiZheThread extends Thread 

{

private Stack stack;

public XiaoFeiZheThread(String name,Stack stack)

{

super(name);

this.stack = stack;

start();

}

@Override

public void run()

{

for(int i=1;i<=200;i++)

{

// 消费者进行弹栈

String s1 = stack.pop();

System.err.println(this.getName() + "第" + i + "次弹栈成功,弹栈的数据是:" + s1);

}

}

}




public class Main 

{

public static void main(String[] args

{

// 创建栈对象

Stack stack = new Stack();

// 创建线程对象

ShenChanZheThread t1 = new ShenChanZheThread("生产者线程", stack);

XiaoFeiZheThread t2 = new XiaoFeiZheThread("消费者线程", stack);

}

}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值