java线程基础知识
一、volatile关键字
为了确保线程具有原子性,可见性,有序性的特点,java使用了一些特殊的操作和关键字来申明某些数据要特别注意。volatile就是其中之一。
当用volatile去声明一个变量的时候,就相当于告诉虚拟机,该变量很有可能被某些线程修改,确保该线程被修改后,相关线程都能清楚这一改动。但是volatile并不能代替锁,它不能保证一些复杂操作的原子性。
public class VolatileTest {
private volatile static int a = 0;
static class ThreadA implements Runnable{
public void run() {
for(int i = 0;i<10000;i++) {
a ++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread threads[] = new Thread[20];
for(int i = 0;i<20;i++) {
threads[i] = new Thread(new ThreadA());
threads[i].start();
}
for(int i = 0;i<20;i++) {
threads[i].join();
}
System.err.println(a);
}
}
上面的代码输入总小于20w,如果a++是原子性的话那么输入应该等于20w。
二、线程组(THreadGroup)
如果系统的线程很多,管理起来很复杂,就可以把线程放在线程组里,线程组相当于一个篮子,用来存放和管理线程。不同线程组的线程不能修改对方线程的数据,在一定程度上保证了数据的安全。
public class ThreadGroupTest {
static class MyThread implements Runnable{
public void run() {
String GroupAndName = Thread.currentThread().getThreadGroup().getName()+"-"+Thread.currentThread().getName();
System.err.println(GroupAndName);
}
}
public static void main(String[] args) {
ThreadGroup tg = new ThreadGroup("MyThreadGroup");
Thread t1 = new Thread(tg,new MyThread(),"m1");
Thread t2 = new Thread(tg,new MyThread(),"m2");
t1.start();
t2.start();
System.err.println(tg.activeCount()); //打印线程组中所有的线程总数(动态的,所以是估计值)
tg.list(); //打印线程组内的所有线程名称
}
}
三、守护线程(Daemon)
守护线程是一种特殊线程,是系统的守护者,在后台默默完成一些系统性的服务,比如垃圾回收线程,JIT线程等可以理解为守护线程,与之相对应的是用户线程,用户线程是系统的工作线程,用户线程完成的是特定的业务操作,如果用户线程全部结束,该系统就无事可做,守护线程也就没有对应要守护的对象了。简而言之就是当系统中只有守护线程时,java虚拟机就会自动退出。下面就是一个守护线程的例子。
public class DaemonTest {
public static class DaemonThread implements Runnable{
private boolean count = true;
public void run() {
while(count) {
System.err.println("this is a DaemonThread");
count = true;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DaemonThread());
t1.setDaemon(true);
t1.start();
Thread.sleep(2000);
}
}
当主线程休眠两秒后退出时,系统没有其余的用户线程,因此守护线程也紧接着退出。设置一个线程为守护线程时,一定要在线程start之前设置,否则会设置失败,本该设置为守护线程的线程会被作为用户线程运行。
三、线程优先级
线程可以设置自己的优先级,优先级高的线程在竞争cpu资源的时候有更大的优势。但设置线程优先级可能出现低优先级线程一直抢占不到资源,从而产生饥饿,所以在有严格要求的场合,还是需要自己在应用层解决线程调度问题。
一般在java中线程的优先级由数字1-10表示,数字越大,优先级越高。下面的代码体现了线程优先级在实际运用中的作用。
public class PriorityTest {
static class HighPriority implements Runnable{
static int count = 0;
public void run() {
while(true) {
synchronized(PriorityTest.class) {
count++;
if(count>1000000) {
System.err.println("高优先级线程计算完成");
break;
}
}
}
}
}
static class LowPriority implements Runnable{
static int count = 0;
public void run() {
while(true) {
synchronized(PriorityTest.class) {
count++;
if(count>1000000) {
System.err.println("低优先级线程计算完成");
break;
}
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new HighPriority());
Thread t2 = new Thread(new LowPriority());
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
}
}
设置为高优先级的线程有较大的概率可以先完成计算,但并非绝对。
四、synchronized关键字
并发程序下很容易产生线程安全问题,即多个线程同时对一个数据进行改动时,会产生冲突。因此,在一个线程对共享数据进行读写等操作时,要求其他线程不能对共享数据进行读写等操作。java中使用synchronized关键字来实现这一功能。
synchronized关键字的作用是实现线程的同步。对同步的代码加锁,在同一时间只能有一个线程对其进行操作。synchronized可以对给定的对象加锁,进入同步代码块之前要获取该对象的锁,也可以直接用于实例方法,相当于对当前实例加锁,进入同步代码块之前要获得当前实例的锁,也可以用于静态方法,相当于对当前类加锁,进入同步代码块钱要获得当前类的锁。synchronized的理解可以回顾下之前的生产者-消费者模型。