线程相关
1.线程组
如果线程数量过多,而且功能分配明确,我们可以把功能一样的线程放入一个线程组中统一进行操作。
/**
* 线程组:将类似功能的线程放入同一组内,便于管理
* @author wsz
* @date 2017年11月27日
*/
class Print1 implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getThreadGroup().getName()+"_"+Thread.currentThread().getName();
while(true) {
System.out.println(name);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Print2 implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getThreadGroup().getName()+"_"+Thread.currentThread().getName();
while(true) {
System.out.println(name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadGroupTest implements Runnable{
public static void main(String[] args) {
ThreadGroup group = new ThreadGroup("ThreadGroup");//线程组及命名
// Thread tg1 = new Thread(group,new ThreadGroupTest(),"tg1");
// Thread tg2 = new Thread(group,new ThreadGroupTest(),"tg2");
Thread tg1 = new Thread(group, new Print1(), "print1");
Thread tg2 = new Thread(group, new Print2(), "print2");
tg1.start();
tg2.start();
System.out.println(group.activeCount());//估值活动线程数目
group.list();//线程组中线程信息
}
@Override
public void run() {
String name = Thread.currentThread().getThreadGroup().getName()+"_"+Thread.currentThread().getName();
while(true) {
System.out.println(name);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.守护线程
守护线程一般用于系统性的服务,比如垃圾回收线程、JIT线程。当系统中的用户线程全部结束工作后,守护线程也将结束工作。
注意:守护线程的设置必须在开启之前,否则将出现异常。
/**
* 守护线程:一般在后台完成系统性的服务,比如垃圾回收线程、JIT线程。
* 工作线程:完成程序业务的线程。当期线程结束,那么守护线程也将停止工作。因此当程序中只存在守护线程时,java虚拟机就会退出。
* @author wsz
* @date 2017年11月27日
*/
class Daemon1 implements Runnable{
@Override
public void run() {
while(true) {
System.out.println("alive");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class DaemonThread {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Daemon1());
//设置为守护线程,且必须在线程start之前设置,否则出现java.lang.IllegalThreadStateException异常,
//且线程将一直打印“alive”,因为当前程序只有main主线程,当main休眠2秒后程序将退出。
//如果t不是守护线程,在main结束后,t线程仍将一直打印不会结束。
t.setDaemon(true);
t.start();
// t.setDaemon(true); //出现异常,且不会终止,将一直打印。
t.sleep(2000);
}
}
3.线程优先级
优先级别高的线程有更高的机会获取足够的资源运行。使用1-10表示线程优先级,数字越大线程优先级越高。Thread类中定义了三个静态标量。 /**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
/**
* 线程优先级:数字1-10,数字越大级别越高
* @author wsz
* @date 2017年11月28日
*/
public class PriorityThread {
public final static Object object = new Object();
class HighThread implements Runnable{
int count = 0;
@Override
public void run() {
while(true) {
synchronized (object) {
count++;
if(count > 200000) {
System.out.println("HighThread is ok");
break;
}
}
}
}
}
class LowThread implements Runnable{
int count = 0;
@Override
public void run() {
while(true) {
synchronized (object) {
count++;
if(count > 200000) {
System.out.println("LowThread is ok");
break;
}
}
}
}
}
public static void main(String[] args) {
Thread high = new Thread(new PriorityThread().new HighThread());
Thread low = new Thread(new PriorityThread().new LowThread());
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
high.start();
low.start();
}
}
4.synchronized
程序并行是为了获取更高的执行效率,但必须得以准确性为前提。
volatile关键字并不能真正保证线程安全。只能保证一个线程修改数据后,其他线程能够看到这次改动;但当多个线程同时修改某一个数据时,依旧会发生冲突。
public class AddThread implements Runnable{
static AddThread instance = new AddThread();
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);//count值一般总是小于2个线程累加之和的。即使使用了volatile
}
@Override
public void run() {
for(int i = 0; i < 10000 ;i ++) {
count++;
}
}
}
为了实现线程之间的同步可以使用synchronized关键字对同步的代码块进行加锁。当线程进入代码块之前需要先获得对应的锁资源,即可保证线程间的安全性,并确保线程的原子性、可见性(只能由一个线程运行同步块内的程序,不会冲突)、有序性(同步块内程序只能由一个线程运行)。
- 指定加锁对象:对给定对象加锁,进入同步块之前必须先获得该对象的锁资源(注意锁对象的可变性,比如Integer不可变,不能作为加锁对象)
- 直接作用于实例方法:相当于对当前实例加锁,进入同步块之前必须先获得当前实例的锁资源
- 直接作用于静态方法:相当于对当前类加锁,进入同步块之前必须先获得当前类的锁资源
对刚才的代码指定加锁对象,即可保证count的值为20000。
public class AddThread implements Runnable{
static AddThread instance = new AddThread();//同一个对象
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);//count值计算正确为20000
}
@Override
public void run() {
synchronized (instance) {
for(int i = 0; i < 10000 ;i ++) {
count++;
}
}
}
}
作用于实例对象:
public class AddThread implements Runnable{
static AddThread instance = new AddThread();
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
//此时指向同一个实例对象,保证线程关注到同一个对象锁资源上。
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);//count值计算正确为20000
}
@Override
public void run() {
// synchronized (instance) {
// for(int i = 0; i < 10000 ;i ++) {
// count++;
// }
// }
for(int i = 0 ;i < 10000 ; i++) {
increase();
}
}
public synchronized void increase() {//作用于实例方法,
count++;
}
}
如果想新建不同的实例对象并保证线程安全,可以把同步方法设置为static修饰,这样就是对当前类进行加锁,而不是当前实例。
public class AddThread implements Runnable{
static AddThread instance = new AddThread();
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
//此时指向同一个实例对象,保证线程关注到同一个对象锁资源上。
// Thread t1 = new Thread(instance);
// Thread t2 = new Thread(instance);
//此时作用于静态方法,可以实例化不同对象
Thread t1 = new Thread(new AddThread());
Thread t2 = new Thread(new AddThread());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);//count值计算正确为20000
}
@Override
public void run() {
// synchronized (instance) {
// for(int i = 0; i < 10000 ;i ++) {
// count++;
// }
// }
for(int i = 0 ;i < 10000 ; i++) {
increase();
}
}
public static synchronized void increase() {//作用于实例方法,
count++;
}
}
5.集合与线程安全问题
5.1ArrayList
public class ArrayListTest {
static ArrayList<Integer> arry = new ArrayList<Integer>();
class AddList implements Runnable{
@Override
public void run() {
for(int i= 0; i< 10000 ; i++)
arry.add(i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread a1 = new Thread(new ArrayListTest().new AddList());
Thread a2 = new Thread(new ArrayListTest().new AddList());
a1.start();
a2.start();
a1.join();
a2.join();
System.out.println(arry.size());//size值将出现小于20000的情况
}
}
运行上面程序ArrayList将添加数字,可能出现情况:
- 正常结果20000
- 抛出下面的异常信息:在ArrayList扩容时,内部一致性被破坏,但是没有锁保护,另一个线程访问到了不一致的内部状态,出现越界问题。
- 打印结果小于正常值20000:此时保存容器大小的变量被多线程不正常访问,即读写不一致,导致赋值出现为题。
- 解决方法:可以使用线程安全的Vector代替。
Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 10
at java.util.ArrayList.add(ArrayList.java:459)
at arrayList.ArrayListTest$AddList.run(ArrayListTest.java:14)
at java.lang.Thread.run(Thread.java:748)
10002
5.2HashMap
HashMap也不是线程安全的。在并发下容易使得内部结构混乱,比如链变成环。Java8中已规避了成环问题,但仍不建议在多线程环境中使用,可使用线程安全的ConcurrentHashMap。
public class HashMapTest {
static HashMap<String,Object> map = new HashMap<String,Object>();
class AddHashMap implements Runnable{
@Override
public void run() {
for(int i= 0; i< 100 ; i++)
map.put(String.valueOf(i), i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new HashMapTest().new AddHashMap());
Thread t2 = new Thread(new HashMapTest().new AddHashMap());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(map.size());//size值将出现小于200的情况
}
}
最后github代码地址:https://github.com/BeHappyWsz/Thread.git