线程
线程的创建的四种方式:
1继承Thread类
2实现Runnable接口
3.使用Callable和Future来创建
4利用Executer工具类来创建
具体实现我给大家推荐大佬写得很全的:一个写的很好的博客
为什么会出现同步问题:
以下的程序很容易看出最后的n应该是20000,但是实际运行的时候并不是(而且我们采用了Volatile关键字修饰n:即一旦n在一个线程发生了改变,另一个线程可以立即看见):
package pre.guowei.work2;
import java.util.concurrent.CountDownLatch;
public class SysProblem {
private static volatile int n=0;
private final static int count=20;
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(count);//保证最后的n输出时 是20个线程都运行完了
for (int i = 0; i < 20; i++) {
new Thread(()-> {
for (int s = 0; s < 1000; s++) {
n++;
}
countDownLatch.countDown();//count--
}).start();
}
countDownLatch.await();//阻塞等待count减到0
System.out.println(n);
}
}
那么为什么任然会发生n最后不等于20000的情况呢?
是因为我们没有保证n的修改操作的原子性:
在java程序执行的时候把对n的修改分为三步:
1:将n从内存取出
2:修改n的值
3:再将n的值写回内存
只有我们保证了以上的三个操作是原子操作时,n才能做到在多线程里的同步:
为了解决以上的问题实现的同步的方法有:
1:用java.util.concurrent.atomic中的原子变量来定义我们需要的同步变量
package pre.guowei.work2;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class SysProblem {
private static AtomicInteger n=new AtomicInteger();
private final static int count=20;
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(count);//保证最后的n输出时 是20个线程都运行完了
for (int i = 0; i < 20; i++) {
new Thread(()-> {
for (int s = 0; s < 1000; s++) {
n.addAndGet(1);
}
countDownLatch.countDown();//count--
}).start();
}
countDownLatch.await();//阻塞等待count减到0
System.out.println(n);
}
}
2:同步块和同步方法:即加锁对临界区加锁和将方法定义成Synchronized的
同步块:
public void writeEntry(String message) throws IOException {
synchronized (out) {
Date d = new Date();
out.write(d.toString());
out.write('\t');
out.write(message);
out.write("\r\n");
}
}
public synchronized void writeEntry(String message) throws IOException {
Date d = new Date();
out.write(d.toString());
out.write('\t');
out.write(message);
out.write("\r\n");
}
对于刚开始提出的问题 这个方法不好实现:其原因有下
《1.对于线程的run()方法,不能使用Synchronized来修饰,即run()不能是同步方法
《2.对于int类型的n,不能用Synchronized来修饰,只能来修饰对象。
或许有的人会想我们把int改成integer对象不就行了,答案是:并不行。至于为什么不行我这里简单的解释一下(这不是这篇博客的重点):当我们对integer对象进行算数操作的时候,会调用他本身分装的拆箱和装箱方法,在API文档如下里:
就是说计算之后的结果并不是对原对象的修改而是返回了另一个对象,可以说我们每次只锁住了Integer对象”一次“。所以我们说Synchronized不能锁住Integer对象
3:对于一些类我们可以将其编写成线程安全的类,或者我们选择类库里的线程安全的类
这里解释一下什么叫线程安全的类:就是在构建的时候就已经考虑好了多线程下的同步问题的类。如:
HashMap就是线程不安全的类
HashTable就是线程安全的类