为了解决“一边...一边...问题”,可以使用进程或者线程
进程与线程
什么是进程
进程是应用程序运行的单元
什么是线程
线程是进程的单元。在JVM中,线程是抢占式调度的,不是时间片轮转调度的。
进程与线程有什么区别
- 进程有独立的内存空间。线程堆内存共享,栈内存独立。
- 线程资源消耗比进程小。
- 因为一个进程中的多个线程是并发运行的。
- Java程序的进程里至少包含主线程和垃圾回收线程(后台线程)。
并行与并发
并发:微观异步运行,宏观同步运行。可说在 同一“时间段”,不一定同一“时刻”进行。
在单cpu中,在一个时刻只有一个任务在进行,因为其采用时间片轮转调度,每个时间片对于cpu的运行速度而言非常短,所以在宏观上,给人的感觉还是很多个线程任务在同时进行
并行:真真切切的同一个时刻进行的事件。
创建进程
创建一个进程,运行命令“dxdiag”
方式一,使用Runtime
...
Runtime runtime=Runtime.getRuntime();
try{
runtime.exec("dxdiag");
}catch(IOException e){
System.out.println(e.getMessage());
}
...
方式二,使用ProcessBuilder
...
ProcessBuilder process=ProcessBuilder("dxdiag");
//ProcessBuilder process=ProcessBuilder("dxdiag");
//process.command("dxdiag");
try{
process.start();
}catch(IOException e){
System.out.println(e.getMessage());
}
...
创建线程
创建线程主要有两种方式:
1. 继承Thread类
Thread类及其子类称之为 线程类
public class ExtendsThreadDemo {
public static void main(String[] args) {
SingThread sing=new SingThread();
sing.start();
}
}
class SingThread extends Thread {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("01_Singing"+i);
if(i==10)
{
PlayThread play=new PlayThread();
play.start();
}
}
}
}
class PlayThread extends Thread {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("02_playing"+i);
}
}
}
2. 实现Runnable接口
public class ImplementsRunnable {
public static void main(String[] args) throws InterruptedException {
Singable singable=new Singable();
Thread sing=new Thread(singable,"唱歌");
for (int i = 0; i < 50; i++) {
System.out.println("02_playing"+i);
if(i==10){
sing.start();
Thread.sleep(100);
}
}
}
}
class Singable implements Runnable{
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("01_Singing"+i);
}
}
}
3. 两种方式使用匿名内部类创建线程
public static void main(String[] args) {
System.out.println("匿名内部类创建线程!");
new Thread(){
public void run(){
for (int i = 0; i < 50; i++) {
System.out.println("01_Singing"+i);
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("02_playing"+i);
}
}
}, "Play").start();
}
两种创建方式的比较
实现 三个 抢
个 西瓜!
继承Thread方式
public class ExtendsDemo {
public static void main(String[] args) {
new Eat("八戒A").start();
new Eat("八戒B").start();
new Eat("八戒C").start();
}
}
class Eat extends Thread {
private int num = 50;
public Eat(String name) {
super(name);
}
@Override
public void run() {
while (num > 0) {
System.out.println(this.getName() + "吃掉了" + num + "号西瓜!");
int i = num--;
/* try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException("线程休眠失败!",e);
}*/
}
}
}
结果:
八戒B吃掉了50号西瓜!
八戒A吃掉了50号西瓜!
八戒C吃掉了50号西瓜!
...
实现Runnable接口方式
public class ImplementsDemo {
public static void main(String[] args) {
Eatable eatable=new Eatable();
Thread eat_A=new Thread(eatable,"八戒A");
Thread eat_B=new Thread(eatable,"八戒B");
Thread eat_C=new Thread(eatable,"八戒C");
eat_A.start();
eat_B.start();
eat_C.start();
}
}
class Eatable implements Runnable {
private int num = 50;
@Override
public void run() {
while (num > 0) {
System.out.println(Thread.currentThread().getName() + "吃掉了" + num + "号西瓜!");
num--;
}
}
}
结果:
八戒A吃掉了50号西瓜!
八戒A吃掉了49号西瓜!
八戒A吃掉了48号西瓜!
...
继承方式 创建每一线程就创建一个各自的num,三个八戒吃了总数150个西瓜。
实现方式 创建一个对象去构造多个线程,每个线程都共享一个num,真正实现了三个八戒吃了50 个西瓜。
区别:
资源共享;资源不共享
代码设计更优美
操作复杂;操作简单
也许你会想到 关键字 static
public class ExtendsDemo {
public static void main(String[] args) {
new Eat("八戒A").start();
new Eat("八戒B").start();
new Eat("八戒C").start();
}
}
class Eat extends Thread {
private static int num = 50;
public Eat(String name) {
super(name);
}
@Override
public void run() {
while (num > 0) {
System.out.println(this.getName() + "吃掉了" + num + "号西瓜!");
int i = num--;
/* try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException("线程休眠失败!",e);
}*/
}
}
}
这样不就解决了 继承方式创建线程 资源不共享问题了吗?
static 真的好吗?
线程同步与线程安全
对于上述的代码分析:
在run()方法中,三个线程A、B、C
B
while (num > 0) {
A C到此处被网络等原因延迟
System.out.println(this.getName() + "吃掉了" + num + "号西瓜!");
int i = num--;
}
所以,三个线程可能同时打印吃西瓜,如果在num--
前出现线程延迟,一旦延迟结束,则三个线程同时执行num--
,可能有些西瓜就没有被吃掉,在最后还可能出现负数号码的西瓜。
为了解决上述的线程安全问题
- 同步代码块
- 同步方法
- 锁机制
1. 同步代码块
public class SynchronizedBlockDemo {
public static void main(String[] args) {
System.out.println("你好!这里是同步代码块解决线程安全问题!");
Runnable eatable = new Runnable() {
private int num = 50;
@Override
public void run() {
synchronized (this) {
while (num > 0) {
System.out.println(Thread.currentThread().getName() + "吃掉了" + num + "号西瓜");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException("线程休眠出错!", e);
}
num--;
}
}
}
};
Thread thread_A=new Thread(eatable,"八戒A");
Thread thread_B=new Thread(eatable,"八戒B");
Thread thread_C=new Thread(eatable,"八戒C");
thread_A.start();
thread_B.start();
thread_C.start();
}
}
2. 同步方法
public class SynchronizedMethodDemo {
public static void main(String[] args) {
System.out.println("你好!这里是同步方法解决线程安全问题!");
Eatable eatable=new Eatable();
Thread thread_A=new Thread(eatable,"八戒A");
Thread thread_B=new Thread(eatable,"八戒B");
Thread thread_C=new Thread(eatable,"八戒C");
thread_A.start();
thread_B.start();
thread_C.start();
}
}
class Eatable implements Runnable{
private int num=50;
@Override
public synchronized void run() {
while(num>0){
System.out.println(Thread.currentThread().getName()+"吃掉了"+num+"号西瓜!");
try{
Thread.sleep(123);
}catch (InterruptedException e){
throw new RuntimeException("线程休眠失败!",e);
}
num --;
}
}
}
3. 锁机制
- 创建锁 :private final ReentrantLock lock=new ReentrantLock();
- 使用锁:
lock.lock();
try{
//运行的 资源操作 逻辑代码
}finally{
lock.unlock();
}
例子全部代码:
public class LockDemo {
public static void main(String[] args) {
System.out.println("你好!这里是锁的使用!");
Eatable1 eatable1 = new Eatable1();
Thread eat_A = new Thread(eatable1, "八戒A");
Thread eat_B = new Thread(eatable1, "八戒B");
Thread eat_C = new Thread(eatable1, "八戒C");
eat_A.start();
eat_B.start();
eat_C.start();
}
}
class Eatable1 implements Runnable {
private int num = 50;
private final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
lock.lock();
try{
while (num > 0) {
System.out.println(Thread.currentThread().getName() + "吃掉了" + num + "号西瓜!");
try {
Thread.sleep(100);
} catch (Exception e) {
System.out.println(e.getMessage());
}
num--;
}
}finally {
lock.unlock();
}
}
}
为了性能
尽量减小synchronized和lock的作用域.
双重检查机制
while(num>0){
synchronized(this){
if(num>0){
System.out.println(Thread.currentThread().getName()+"吃掉了"+num+"号西瓜!");
try{
Thread.sleep(123);
}catch (InterruptedException e){
throw new RuntimeException("线程休眠失败!",e);
}
num --;
}
}
}
“双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只能用在java5及以上的版本。
由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。
StringBuffer与StringBuilder的区别.
ArrayList和Vector的区别.
HashMap和Hashtable的区别.
StringBuffer 、Vector、 Hashable线程安全
懒汉单例与饿汉单例
懒汉单例 线程不安全
public class SingleLazyDemo {
public static void main(String[] args) {
System.out.println("你好!这里是单例的懒汉模型");
}
}
class SingleLazy {
private static SingleLazy singlelazy = null;
private SingleLazy(){}
public static SingleLazy getInstance() {
if (singlelazy == null) {
//此处存在线程安全问题
singlelazy = new SingleLazy();
}
return singlelazy;
}
}
class SingleLazy_1 {
private static SingleLazy_1 singlelazy1 = null;
private SingleLazy_1(){}
public synchronized static SingleLazy_1 getInstance() {
if (singlelazy1 == null) {
singlelazy1 = new SingleLazy_1();
}
return singlelazy1;
}
}
饿汉单例 线程安全
public class SingleHungeryDemo {
public static void main(String[] args) {
System.out.println("你好!这里是单例的饥汉模型!");
}
}
class SingleHunger {
private static SingleHunger singlehunger = new SingleHunger();
private SingleHunger() {
}
public static SingleHunger getInstance() {
return singlehunger;
}
}
网友评论