java中的线程
创建线程
java中创建一个线程有两种方法,使用类继承实现Thread类实例化调用start方法,或者使用内部类创建
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
Thread thread=new MyThread();
thread.start();
由于Java中的类只支持单继承,在类已经继承一个父类的情况下无法再继承Thread,所以java还提供了Runable接口,可以使用类实现Runable接口来创建线程
public class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
Thread thread1=new Thread(new MyRunable());
thread1.start();
除此之外,java还支持使用内部类匿名创建线程对象
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}.start();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i) ;
}
}
}).start();
特殊的守护线程
通过调用线程对象的setDaemon方法可以设定线程为守护线程,当所有非守护线程死亡,守护线程才会死亡,jvm中经典的守护线程就是gc(垃圾回收)
Thread t1=new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
while (true){
System.out.println("守护线程工作中......");
}
}
};
t2.setDaemon(true);
t1.start();
t2.start();
线程安全问题
当多个对象创建多个线程对象时对象的成员变量相互独立
public class MyThreadTest extends Thread {
private int nums = 10;
@Override
public void run() {
while (nums > 10) {
System.out.println(nums);
nums--;
}
}
}
Thread t1=new MyThreadTest();
Thread t2=new MyThreadTest();
Thread t3=new MyThreadTest();
t1.start();
t2.start();
t3.start();
执行30次
使用多个线程对象代理同一个对象时,成员变量共享,但是当多个线程访问一个共享资源时会出现线程安全问题
public class MyRunableTest implements Runnable {
private int nums=10;
@Override
public void run() {
while (nums>0){
System.out.println(nums);
nums--;
}
}
}
MyRunableTest myRunableTest=new MyRunableTest();
Thread t1=new Thread(myRunableTest);
Thread t2=new Thread(myRunableTest);
t1.start();
t2.start();
执行10多次,因为多个线程可能同时进入循环同时拿到值,就会出现打印了两次只减少了一次
原理
cpu在处理指令时为乱序,在多线程情况下随机执行每个线程的语句,java属于高级编程语言,java底层为c/c++实现,c/c++底层为汇编语言,java中一个语句可能需要执行许多指令才能完成,cpu乱序执行这些指令就会导致线程安全问题
在上述的run方法中可以将指令看为,获取值判断,获取值打印,获取值,计算,写回,在例子中可能的执行顺序为
…
线程1获取值
线程2获取值
线程1计算
线程1写回
线程2计算
线程2写回
…
由于两个线程先后获取到了值,再进行计算写回,导致打印两次同一个值
线程安全问题解决方案
通过使用java内部提供的同步锁可以解决以上问题,通过使用锁可以使方法在同一时间只有一个线程访问,其他线程则被阻塞
同步锁方法
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁代码块
synchronized(同步锁){
需要同步操作的代码
}
线程的等待唤醒机制
线程之间的关系不只有竞争抢夺cpu资源,还能够通过等待唤醒机制进行配合
线程可以使用wait方法与notify方法,实现等待和唤起其他相同锁对象的线程
wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源
notify:则选取所通知对象的 wait set 中的一个线程释放
notifyAll:则释放所通知对象的 wait set 上的全部线程
wait和sleep的区别,wait会释放锁,sleep不会释放
生产者与消费者问题
生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
public class PC {
private int num=10;
public synchronized void in(){
while (true){
if (num<10)
{
System.out.println(num);
num++;
notifyAll();
}
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public synchronized void out() {
while (true){
if (num > 0) {
System.out.println(num);
num--;
notifyAll();
}
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
PC pc=new PC();
new Thread(){
@Override
public void run() {
pc.in();
}
}.start();
new Thread(){
@Override
public void run() {
pc.in();
}
}.start();new Thread(){
@Override
public void run() {
pc.out();
}
}.start();
new Thread(){
@Override
public void run() {
pc.out();
}
}.start();
new Thread(){
@Override
public void run() {
pc.out();
}
}.start();
}