这是一个简单的例子,简单说明了多线程上的一个简单的应用。
最近一直在学习java,记录自己的学习历程和总结,以便于将来的复习,同时也希望多多指点,共同学习,共同进步,接下来的时候。会定期进行更新,
- 当main方法调用结束之后,主线程进入死亡状态。我们也可以在main方法中编写其他的代码,看到三个线程交替执行,
- 主线程也就是一个线程,就是JVm创建的而已,真正重要的是 线程调度器。也就是一段代码而已。覆盖的子类不能将异常扩大。
- 线程里出现,未检查异常。导致线程死亡。
- 多个操作要同时执行的时候,必须使用多线程,
- 只要用户线程没有结束。则JVm不会主动关闭的。
- main是在主线程中来执行的。main就是主线程的目标对象,
- 线程创建的时候,就都是默认用户线程,可以把一个用户线程标记为守护线程。
- 标记守护线程的操作。一定要在启动线程之前标记。否则会出现无效的线程
- dt.SetDeamon(true)
- JVM发现当虚拟机只有守护线程在运行的时候,JVm会强行的停止守护线程,越就是说 jVM不会让守护线程一致运行的。
- 守护线程仅仅是为了 衬托用户线程。只要了解就可以了。
- 每个线程都有一个优先级(1~10级).优先级越高,被线程调度器选中的机会就越高。
- 获得线程的优先级:thread.getPriority()
- 在目标对象中获取正在执行的该目标的对象: Thread.currentThread( )
- 在启动线程之前,通过线程的实例方法setPriority()改变线程的优先级.改变优先级之前要在启动线程之前完成,否则会出现异常。
- 线程的优先级用默认的5级就可以了,千万不要改变.
- 暂停当前正在执行的线程对象(yied),该线程对象重新回到就绪队列。该线程有可能被调度器选中,即使这样的话,也是让其他线程更多的获的运行机会。
- .sleep(1000)睡眠时间。线程在睡觉时候 不会回到就绪队列,睡醒的时候才会回到就绪序列。
- 线程的sleep() yield方法都是静态方法。静态方法调用的时候和对象没有关系, 在哪个线程中调用了线程的sleep方法,哪个线程就会睡觉, 在main方法中,调用ti对象的sleep方法,但是和t1没有关系,在主线程中调用的sleep方法,所以是主线程睡觉。
- 线程的优先级 千万不要改,用默认的5级就可以了,实际开发中用默认优先级。
- 线程在sleep()期间是不会回到就绪队列的.
- 我们在主线程中调用了thread的jion()方法,直到另一个线程执行完毕,主线程才会开始执行。
- 在主线程中调用Thread.jion()方法。
- 线程的stop()方法已经废弃了。
- 在控制线程结束的时候,在线程中设置boolean型的开关量,让线程的循环和开关量结合起来。
- 线程的sleep方法和yield方法都是静态方法,静态方法调用的时候和对象无关。
- 线程的yield方法会让当前线程放弃CPU,但是该线程会马上回到就绪队列,有可能被调度器重新选中。
- 线程的yield方法会让其他线程获得更多的运行机会。
- 我们在主线程中调用了t1线程的实例方法jion,即:t1.jion(),则主线程会被阻塞,直到t1线程运行完毕,主线程才会接触阻塞,重新回到就绪队列。
- 换句话说:只要t1线程没有运行结束,主线程就不会运行。
- 创建好线程对象之后,通过setDaemon()方法把线程标记为守护线程。
- 当JVM发现只有守护线程运行的时候,JVM会主动的关闭守护线程,然后关闭JVM。
- 守护线程 对应的是 用户线程, 线程创建的时候默认是用户线程。
- 用户线程没有结束,JVM是不会关闭的。
- synchronized(this)中的this是当前对象中指向当前对象的引用,使用当前对象来当作锁的。什么东西可以当作锁呢?任何对象都可以当作锁,
- 锁默认有时候是开着呢 当线程进入锁的时候,锁就被锁上了,别的线程就不能访问这个同步块,只有当这个线程执行完这个同步块的时候,锁被打开。别的线程才能进来执行同步块。
- 注意,线程获得锁之后,进入同步块,即使执行了sleep活着yield,线程是不会放锁的。
- 注意不是线程的。不能直接调用线程对象,要获取正在执行的线程的对象。 thread.currentThread() 获取当前正在执行的线程的对象。
- synchronized可以锁住整个方法么?可以的。把synchronized放到方法的返回类型之前,synchronized当作修饰符来使用,专门加在方法中用来控制线程同步的。 言外之意就是:可以修饰方法,表示这个方法被锁。但是只能修饰方法!
- 何时需要线程同步? 只有当多个线程 同时修改 相同数据的时候,才需要给数据加锁。
- 线程同步不能滥用,因为线程同步是以牺牲速度作为而代价的。
- ArrayList ,HashMap StringBuilder : 新集合 , 速度快, 非线程安全的(就是说方法没有加锁)
- Vector ,Hashtable StringBuffer : 老集合 , 速度慢, 线程安全的(就是说 方法上加锁了)
- 多个线程同时访问同一个对象的不同方法,这才是实际开发的正常现象。
- 生产者和消费者模式: ⚠️
- (1)注意:锁和铃铛必须是同一个对象,否则会 出现 无效的监视器异常,
- (2)注意:线程必须获得锁之后(必须进入同步块),才能调用obj.wait(),否则会出现异常;
- (3)注意:生产者的次数和消费者的次数必须要一致;
- (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);
}
}