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

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



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





java 多线程中的方法用加锁吗 java多线程锁的使用_主线程


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方法,哪个线程就会睡觉,
  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.在数据类中设置状态变量,让状态变量形成开关的状态 

class Stack 
 
private String arr[] = new
private  int index = -1;   // 栈的索引
private boolean  dataIsReady =  false;  // 开关控制变量,就是数据有没有准备好

/**
*  压栈的方法
* @param
*/
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
{
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
 {
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
 {
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);
}
}
 }
 
  
 

 
 
 class Main 
 {
public  static void main(String[]  args) 
{
// 创建栈对象
Stack stack = new

// 创建线程对象
ShenChanZheThread  t1 = new ShenChanZheThread("生产者线程", stack);
XiaoFeiZheThread  t2 = new XiaoFeiZheThread("消费者线程", stack);
}
 }