进程和线程的区别
进程:操作系统应用程序。占用系统的 cpu 和内存
线程:是进程更小的执行单元。一个进程可以有多个线程。线程与线程之间是相互独立的,但是是要共用进程的资源。
区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中,这些线程可以共享数据,因此线程间的通信比较简单,消耗的系统开销也相对较小。
Thread 线程类
构造方法
1 2
| Thread thread = new Thread(); Thread thread = new Thread(线程对象,"线程名字");
|
静态方法
1 2 3 4 5
| "查看线程最大级别":Thread.MAX_PRIORITY
"查看线程最小级别":Thread.MIN_PRIORITY
"查看线程默认级别":Thread.NORM_PRIORITY
|
常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| "让线程开始执行":thread.start(); "自动调用线程的run()方法"
"返回线程的名字":thread.getName();
"返回线程的优先级":thread.getPriority();
"等待线程死亡":thread.join(); "等待线程死亡":thread.join(毫秒);
"给线程设置名字":thread.setName(名字);
"让线程睡眠":thread.sleep(毫米数); "当前线程让出强占cpu的机会 自己马上处于就绪状态":thread.yield();
|
线程状态
- 新建状态(New):使用new 关键字创建线程对象,仅仅被分配了内存
- 就绪状态(Ready):线程对象被创建后,等待它的 start 方法被调用,以获得 CPU 的使用权
- 运行状态(Running):执行 run 方法,此时的线程的对象正占用 CPU
- 睡眠状态(Sleeping):调用 sleep 方法,线程被暂停,睡眠时间结束后,线程回到就绪状态,睡眠状态的线程不占用 CPU
- 死亡状态(Dead):run 方法执行完毕后,线程进入死亡状态
- 阻塞状态(Blocked):线程由于某些事件(如等待键盘输入)放弃 CPU,暂停运行,直到线程重新进入就绪状态,才有机会转到运行状态
创建多线程的方法
继承 Thread 类
实现步骤:
- 创建一个 Thread 类的子类
- 在 Thread 类的子类中重写 Thread 类中的 run 方法,设置线程任务
- 创建 Thread 类的子类对象
- 调用 Thread 类中的方法 start 方法,开启新的线程,执行 run 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyThread extends Thread { @Override public void run() { }
public static void main(String[] args) { MyThread et1 = new MyThread(); et1.start(); } }
|
实现 Runnable+Thread 类的带参构造
实现步骤:
- 创建一个 Runnable 接口的实现类
- 在实现类中重写 Runnable 接口的 run 方法,设置线程任务
- 创建一个 Runnable 接口的实现类对象
- 创建 Thread 类对象,构造方法中传递 Runnable 接口的实现类对象
- 调研 Thread 类中的 start 方法,开启新的线程执行 run 方法
实现 Runnable 接口创建多线程程序的好处:
- 避免了单继承的局限性,一个类只能继承一个类,类继承了 Thread 类就不能继承其他的类,实现了 Runnable 接口,还可以继承其他的类,实现其他的接口
- **增强了程序的扩展性,降低了程序的耦合性(解耦)**,实现 Runnable 接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyRunnable implements Runnable{ @Override public void run() { } public static void main(String[] args) { Thread t1 = new Thread(new MyRunnable()); t1.start(); } }
|
实现 Callable+Future 模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class CallableTest { public static void main(String[] args) throws Exception { Callable<Integer> call = new Callable<Integer>() { @Override public Integer call() throws Exception { return 1; } }; FutureTask<Integer> task = new FutureTask<>(call); Thread t = new Thread(task); t.start(); } }
|
匿名内部类
匿名内部类作用:简化代码
- 把子类继承父类,重写父类的方法,创建子类对象合一步完成
- 把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
- 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class App { public static void main(String[] args){ new Thread(new Runnable() { @Override public void run() { } }).start();
new Thread(){ @Override public void run(){ } }.start(); } }
|
java.util.Timer 工具类
1 2 3 4 5 6 7 8 9 10 11
| public class TimerTest { public static void main(String[] args) throws Exception { new Timer().schedule(new TimerTask() { @Override public void run() { } }, 10000,3000); } }
|
java.util.concurrent.Executors 工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ThreadPoolTest { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(10); while(true) { threadPool.execute(new Runnable() { @Override public void run() { } }); } } }
|
线程同步方法
同步代码块
用synchronized(互斥锁)把代码包裹起来,必须是同一把锁
注意:
- 通过代码块中的锁对象,可以使用任意的对象
- 但是必须保证多个线程使用的锁对象是同一个
- 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
- synchronized锁定的是一个对象,不是代码块
- 一个synchronized代码块是原子操作
- synchronized的锁是可重入的,即当前线程内,一个同步方法调用另一个同步方法或者子类同步方法调用父类同步方法,是可以再次获得锁
1 2 3
| synchornized("锁"){ "出现线程安全的逻辑" }
|
同步方法
- 在方法的定义上面加synchronized 关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| "这里锁定的是当前对象" public synchronized void sale() {"对象锁 (必须是同一把锁 saleWindow)" if (tickets <= 30) { System.out.println(Thread.currentThread().getName() + "卖了第" + tickets + "张"); tickets++; } try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } }
private Object o = new Object(); private int count = 1; public void sale2(){ "上锁" synchronized(o){ count++; } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| private int count = 1;
public synchronized static void m(){ count++: }
public static void n(){ synchronized(T.class){ count++; } }
|
1 2 3 4 5 6 7 8 9
| synchronized void t1(){ t2(); }
synchronized void t2(){ }
|
Lock+ReentranLock 手动上锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private Integer tickets = 1; "创建一把锁" private Lock lock = new ReentrantLock(); public void run() { for (int i = 1; i <= 100; i++) { "手动上锁" lock.lock();
if (tickets <= 100) { tickets++; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } "手动解锁" lock.unlock(); } }
|
sleep、yield、join 和 wait
- sleep 和 yield 都是 Thread 类的静态方法,都会使当前处于运行状态的线程放弃 CPU,但两者的区别在于:
- sleep 给其它线程运行的机会,但不考虑其它线程的优先级;但 yield 只会让位给相同或更高优先级的线程
- 当线程执行了 sleep 方法后,将转到阻塞状态,而执行了 yield 方法之后,则转到就绪状态
- sleep 方法有可能抛出异常,而 yield 则没有,在一般情况下,我们更建议使用 sleep 方法
- join 方法用于等待其它线程结束,当前运行的线程可以调用另一线程的 join 方法,当前运行线程将转到阻塞状态,直至另一线程执行结束,它才会恢复运行
- wait 方法是 Object 类的方法,让当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法才会执行,必须是锁对象才能调用
线程的生命周期
内存可见性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); new Thread(td).start(); while(true){ if(td.isFlag){ System.out.println("-----") break; } } }
class ThreadDemo implements Runnable { private boolean flag = false
@Override public void run(){ try { Thread.sleep(200); } catch (Exception e){} flag = true; } }
|
volatile关键字
当多个线程进行操作共享数据时,可以保证内存中的数据可见,相较于synchronized是一种较为轻量级的同步策略
注意:
- volatile不具备互斥性
- volatile不能保证变量的原子性
原子变量
jdk1.5后java.util.concurrent.atomic包提供了常用的原子变量
功能:
- 使用volatile保证内存可见性
- CAS(Compare-And-Swap)算法保证数据的原子性
CAS
是硬件对于并发操作共享数据的支持,包含了三个操作数
内存值V,预估值A,更新值B
,并且当V == A时,V = B,否则,将不做任何操作
CountDownLatch
闭锁,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行
相关文章
JAVA基础
JDBC
JUC