两处:①是idea安装目录所在文件夹;
②是c盘安装路径
程序面板——>卸载; 找到idea,右键卸载,全部勾选。卸载后最好重启电脑再重装。
.exe下载自定义安装;.zip解压直接使用
JDK:开发+运行
JRE:运行代码
安装过程:
勾选64位;不勾选所有文件关联;等待安装。
安装目录下: bin——>vmoptions文件打开:
-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=512m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-XX:CICompilerCount=2
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-ea
-Dsun.io.useCanonCaches=false
-Djdk.http.auth.tunneling.disabledSchemes=""
-Djdk.attach.allowAttachSelf=true
-Djdk.module.illegalAccess.silent=true
-Dkotlinx.coroutines.debug=off
①16G内存的电脑可设置为 -Xms512m(设置初始内存数,可提高java程序启动速度)
②同样可设为 -Xmx1500m (设置最大内存数,提高该值,可减少内存Garage收集的频率,提高程序性能)
③-XX:ReservedCodeCacheSize=512m (表示保留代码占用的内存容量)目前不用改了
官方提供的IDEA插件下载地址:https://plugins.jetbrains.com/idea
创建java工程,选择好JDK,选择好所存放目录。
tips:输入要输出的语句.sout回车自动创建好输出代码。
在project工程名右键创建一个模块module,module下再创建功能模块及类等。
但是注意,idea project表示一个项目,module仅仅是模块,不是项目。
进入settings设置界面:
Appearance & Behavior
Editor——>Color Scheme 获取更多主题,网站下载:http://www.riaway.com/ 下载好后,导入:file——>import settings——>选中下载的jar文件,一路确认,重启即可。
方式2安装插件。
settings——>Editor——>General中勾选Change font size with Ctrl+鼠标
Editor——>General——>Appearance——>Other勾选show quick documentation on mouse more 后面是反应时间ms
Editor——>General——>Auto import——>两个都勾选,选择Always
Editor——>General——>Appearance——>show line numbers + show method separators都勾选
idea默认区分大小写,比如输入stringBuffer,idea默认不会提示和进行代码补充;除非我们输入StringBuffer就可以进行提示和补充。不想区分大小写,改为none即可。
Editor——>General——>Code Completion, 选择none。或者去掉勾选Match case
打开很多文件时,idea默认把所有打开的文件名tab单行显示,单行会隐藏超过界面部分的tab,效率不高,找文件不方便。
Editor——>General——>Editor Tabs 取消选中show tabs in one row
Editor——>Font 里,font、size、line spacing自定义。
Editor——>Color Scheme——>
Editor——>Color Scheme——>Console Font
Editor——>Color Scheme——>Language Defaults——>Comments里自定义。
Editor——>CodeStyle——>Java——>横栏里点Imports,最下方两个框输入个数自定义。
Editor——>File and Code Templates
Editor——>File Encodings 选择utf-8
可以在编辑区右下角设置编码
Build,Execution,Deployment(构建,执行,部署)——>Compiler右边两项,build project automatically和compile independent modules in parallel选中打勾,即源文件修改后自动编译。
顶部栏file——>power save mode 勾选后就没有代码提示等操作。不要误选。
右键点击编辑区某一个java文件名,选择split right或者split down分别表示垂直左右显示和水平上下显示。
Settings——>Keymap,快捷键设置,右侧搜索框输入要实现的功能,显示相关快捷键。搜索框右侧放大镜输入快捷键显示执行什么功能。
也可以导入其他编辑器的快捷键。在上方选择。
单行注释: Ctrl + /
多行注释: Ctrl + Shift + /
向下移动行(显示被界面遮挡行):Alt + ↓
向上移动行(显示被界面遮挡行):Alt + ↑
提示补全: Alt + /
复制这行到下一行: Ctrl + D
复制Ctrl + C
粘贴 Ctrl + V
剪切 Ctrl + X
保存 Ctrl + S
撤销 Ctrl + Z
反撤销 Ctrl + Y
全选 Ctrl + A
选中数行,整体向后移动 tab
选中数行,整体向前移动 shift + tab
查看类的继承关系: Ctrl + H
查找 : Ctrl + F
查找文件: 双击shift
添加try/catch或者其他语句块(Surround with):Alt + Shift + Enter !!!
等等。
① Editor——>General——>Postfix Completion只能用,固定的
代码缩写自动调用模板补全
如sout 5.for 等等
soutp回车输出形参值
②Editor——>Live Templates很多模板 ,可以修改或自己添加
如main方法 psvm
程序:未完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码(没有运行起来),静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程。有它自身的生命周期
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
并行:多个CPU同时执行多个任务。比如:过个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务,比如:,秒杀、多个人做一件事。
单核CPU情况下,同时发送两个文件比发完一个再发一个要慢。(因为单核看似同时,其实是切换来执行)。
多核下,同时发送更快。
何时需要多线程?
一条线执行下来的是单线程。
Java的jvm允许程序运行多个线程,通过java.lang.Thread类来体现。
Thread类的特性
①每个线程都是通过某个特定的Thread对象的run()方法来完成操作的,经常把run()方法的主体成为线程体
②通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。
//多线程的创建方式一:继承Thread类
/*
* 1. 创建一个继承于Thread类的子类
* 2. 重写Thread类的run()
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start()
* */
//遍历100以内的偶数
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
//2. 重写Thread类的run()
@Override
public void run(){
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest01{
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4. 通过此对象调用start()
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 != 0){
System.out.println(i);
}
}
}
}
分析:t1.start()执行到这里还是主线程执行的;之后run方法自动执行是新线程执行的;下面的for循环还是主线程执行的。主线程执行得比较快就会先输出奇数;再输出偶数。
start()方法的作用:①启动当前线程②调用当前线程的run()方法
本来要执行Thread类的run()方法,但同时子类重写了run()方法,所以调用了子类的run()方法。
问题1:能否直接使用t1.run()开启新的线程,不适用start()???
答:显然不能。java规定start()方法启动线程,直接调用run()方法只是在执行该方法,并不是多线程。
获取当前线程名:Thread.currentThread().getName()
问题2:再启动一个线程,遍历执行100以内的偶数?
答:会报异常。因为start()方法只能去调用一次。这时候需要重新新建一个线程对象t2,来调用start()方法。
Thread类的有关方法:
/*
测试Thread类常用方法
*/
class Thread01 extends Thread{
@Override
public void run(){
for (int i = 0; i < 20; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
Thread01 t1 = new Thread01();
// 给线程起名
t1.setName("线程1");
t1.start();
//给主线程命名
Thread.currentThread().setName("main主线程");
System.out.println(Thread.currentThread().getName() + ":" + "哈哈哈");
}
}
给线程起名方式一:t1.setName(“线程1”);
给线程起名方式二:
//在线程子类中通过构造器重载给线程命名
public Thread01(String name){
super(name);
}
//main主方法里new新的thread对象时将名字作为参数传进去。
Thread01 t1 = new Thread01("thread000");
时间片切换方式
抢占式:高优先级线程抢占CPU
同优先级线程组成先进先出队列,先到先服务,使用时间片策略。
对高优先级,使用优先调度的抢占式策略。
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 线程默认的优先级
getPriority():返回线程优先值
setPriority():改变线程的优先级
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定排在高优先级线程之后才被调用
/*
例子:创建三个窗口卖票,总票数为100张
存在线程安全问题,待解决。
*/
class Window extends Thread{
private static int ticket = 100; //声明为静态,只有一份共用
@Override
public void run(){
while (true){
if (ticket > 0){
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
/*
创建多线程方式二:实现runnable接口
1. 创建一个实现了Runnable接口的类
2. 实现类去实现Runnable中的抽象方法:run()
3. 创建实现类的对象
4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5. 通过Thread类的对象调用tart()s
*/
//1. 创建一个实现了Runnable接口的类
class MThread implements Runnable{
//2. 实现类去实现Runnable中的抽象方法:run()
@Override
public void run(){
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class RunnableImplTest {
public static void main(String[] args) {
//3. 创建实现类的对象
MThread t1 = new MThread();
//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread tt1 = new Thread(t1);
//5. 通过Thread类的对象调用start()
tt1.setName("线程1");
tt1.start(); //为什么调用当前线程的run却执行了重写的run?
//因为当前线程run里面调用了runnable类型的target的run()方法,此时的target就是传进来的参数t1
Thread tt2 = new Thread(t1);
tt2.setName("线程2");
tt2.start();
}
}
/*
例子:创建三个窗口卖票,总票数为100张,使用Runnable接口的方式
存在线程安全问题,待解决。
*/
class Window1 implements Runnable{
private int ticket = 100; //这里没加static,仍然是同一个ticket,原因只new了一个对象t1作为参数传进去。
@Override
public void run(){
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
//三者始终使用同一对象
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.start();
t2.start();
t3.start();
}
}
接口实现更推荐;继承有局限性,无法再继承其他类
另外接口实现方式不用将变量声明为static;
其实,源码中,Thread类本身实现了Runnable接口;两种方式都需要将线程要执行的逻辑重写在run()方法。
JDK中用Thread.State类定义了线程的几种状态。
一个完整的生命周期经历的几种状态:
问题:
案例:
①实现Runnable接口的方式
三个窗口卖票,总共100张, 会出现三个100。出现重票;
当ticket > 0时加sleep让线程睡一段时间,再输出,此时出现每个窗口都能卖100张票/错票;
等等。
问题的原因?
当某个线程操作车票过程中,操作尚未完成时,其他线程参与进来,也操作车票。
如何解决?
当一个线程a在操作票时,其他线程不能参与进来,直到该线程a操作完后,其他线程才可以开始操作ticket,即使a出现了阻塞,也不能改变。
在Java中,通过同步机制解决线程安全问题。
方式一:同步代码块
关键字:
synchronized(同步监视器){
//需要被同步的代码
}
tips:操作共享数据的代码,即需要被同步的代码。
共享数据:多个线程共同操作的变量,如卖票中的ticket
同步监视器:俗称 锁。任何一个类的对象都可以充当锁。
补充:可以使用this替代new的类对象(前提this是唯一)
/*
例子:创建三个窗口卖票,总票数为100张,使用Runnable接口的方式
存在线程安全问题,待解决。
使用synchronized关键字
1. 包住涉及到共享数据的代码块 包的不能多也不能少。
2, new一个对象充当锁
3. 要求:多个线程共用同一把锁,所以锁(对象)的声明位置很重要!!!(匿名类对象也不行)
4. 同步方式好处:解决线程安全问题;操作同步代码时,只能有一个线程参与,其他线程等待,相当于单线程的过程,效率低。-- 缺点。
*/
class Window1 implements Runnable{
private int ticket = 100;
Object obj = new Object(); // 2. 这里随机实例化一个类对象充当下面的锁
//可以不new新的对象,直接用this
@Override
public void run(){
while (true) { //此处可替换为this
synchronized (obj) { //1. 下面都是需要被同步的代码
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
//三者始终使用同一对象
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.start();
t2.start();
t3.start();
}
}
sleep一段时间,同样出现重票问题。
使用synchronized关键字。如上篇方式解决,结果仍然有问题。原因:new了三个对象,obj对象不唯一。
解决:将obj声明为static类型。
补充:这里慎用this充当obj,(除非this唯一),可以用当前类名.class替代
/*
例子:创建三个窗口卖票,总票数为100张,使用继承Thread类的方式
存在线程安全问题,待解决。
同样synchronized关键字
*/
class Window extends Thread{
private static int ticket = 100; //声明为静态,只有一份共用
private static Object obj = new Object(); //obj对象声明为static类型!
@Override
public void run(){
while (true){//此处不能用this替代obj,因为有三个对象。但是可以有另一种替代,即window.class(类名.class)(拿当前类充当,证明了类也是对象!),避免new对象
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window(); //声明了三个类对象
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
改进:有没有相对简单的对象,不用new一个对象的方式?
当前对象。this。即当前类的对象。但是Thread类方式里不能用这个,因为有三个对象。
那么有一个替代方式,即当前类名.class替代obj。
即:如果操作共享数据的代码完整的生命在一个方法中,我们就将该方法声明为同步的。
同步方法:即在该方法返回类型前加synchronized关键字。
/*
使用同步方法解决实现Runnable接口的线程安全问题
*/
class Window2 implements Runnable{
private int ticket = 100; //这里没加static,仍然是同一个ticket,原因只new了一个对象w2作为参数传进去。
private boolean flag = true;
@Override
public void run(){
while (flag) {
show();
}
}
private synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
}else flag = false;
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 w2 = new Window2();
//三者始终使用同一对象
Thread t1 = new Thread(w2);
Thread t2 = new Thread(w2);
Thread t3 = new Thread(w2);
t1.start();
t2.start();
t3.start();
}
}
/*
例子:创建三个窗口卖票,总票数为100张,使用继承Thread类的方式
存在线程安全问题,待解决。
使用同步方法来解决继承Thread类方式的线程安全问题
1. 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
2. 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身。
*/
class Window3 extends Thread{
private static int ticket = 100; //声明为静态,只有一份共用
@Override
public void run(){
while (true){
show();
}
}
private static synchronized void show(){ //写为static类型,此时同步监视器唯一,但不是this,是当前类Window3,当前类唯一
//private synchronized void show(){ //此时同步监视器t1,t2,t3不安全
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 t1 = new Window3();
Window3 t2 = new Window3();
Window3 t3 = new Window3();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
懒汉式是不安全的;饿汉式是安全的。
懒汉式改进。
/*
使用同步机制将单例模式中的懒汉式改写为线程安全的。
加synchronized,静态方法同步锁是类本身
*/
public class BankTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
// public static synchronized Bank getInstance(){
// if (instance == null){
// instance = new Bank();
// }
// return instance;
// }//与下面雷同,只是同步方法和同步代码块的区别
public static Bank getInstance() {
//方式一:效率不高,多个线程只有第一个进去初始化后,后面每个线程还需要继续排队返回instance
// synchronized (Bank.class) {
// if (instance == null) {
// instance = new Bank();
// }
// return instance;
// }
//方式二:稍高 外层加一个判断,双检锁,后来的线程不必再排队进来
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
/*
演示线程的死锁问题
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run(){
synchronized (s1){
s1.append("a");
s2.append("1");
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
//输出 这种会出现死锁,只是概率小一些。另外输出结果不唯一,也可能限制性下面的结果为cd 34 cdab 3412
ab
12
abcd
1234
增加死锁的概率:
sleep 100
/*
演示线程的死锁问题
1. 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2. 说明:死锁出现,无异常无报错,阻塞,无法继续。
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run(){
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
//此时运行后既不报错也不输出任何东西,卡住一直在运行
例2:
class A{
public synchronized void foo(C b) {
System.out.println("当前线程名:" + Thread.currentThread().getName()
+ "进入了A实例的foo方法");
try {
Thread.sleep(100);
} catch (InterruptedException ex){
ex.printStackTrace();
}
System.out.println("当前线程名:" + Thread.currentThread().getName()
+ "企图调用C实例的last方法");
b.last();
}
public synchronized void last(){
System.out.println("进入了A类的last方法内部");
}
}
class C{
public synchronized void bar(A a) {
System.out.println("当前线程名:" + Thread.currentThread().getName()
+ "进入了C实例的bar方法");
try {
Thread.sleep(200);
} catch (InterruptedException ex){
ex.printStackTrace();
}
System.out.println("当前线程名:" + Thread.currentThread().getName()
+ "企图调用A实例的last方法");
a.last();
}
public synchronized void last(){
System.out.println("进入了B类的last方法内部");
}
}
public class ThreadTest2 implements Runnable{
A a = new A();
C b = new C();
public void init(){
Thread.currentThread().setName("主线程");
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run(){
Thread.currentThread().setName("副线程");
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
ThreadTest2 d1 = new ThreadTest2();
new Thread(d1).start(); //调分线程run方法
d1.init(); //调主线程init方法
}
}
/*
解决线程安全问题的方式三:Lock锁 -----JDK5定义
*/
class Window4 implements Runnable{
private int ticket = 100;
//①实例化一个lock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run(){
while (true){
try {
//2. 调用加锁方法Lock()
lock.lock();
if (ticket > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
lock.unlock(); //3。调用解锁的方法
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window4 w = new Window4();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同:二者都解决线程安全问题
不同: synchronized执行完响应逻辑后自动释放同步监视器;lock需要手动启动同步(lock())和结束同步(unlock())。
存在线程安全问题的代码:
/*
题目:银行有一个账户。
有两个储户分别向该账户存3000元,每次存1000,存3次。每次存完打印该账户余额。
分析:
1. 是否为多线程问题? 是,两个储户线程
2. 是否有共享数据? 是,账户
3. 是否有线程安全问题? 是
4. 如何解决? 同步机制3种方式 Thread、runnable、lock
*/
class Account{
private double balance;
public Account(double acct){
this.balance = balance;
}
public void storeMoney(double money){
if (money > 0){
balance += money;
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 存钱成功, 余额为:" + balance);
}
}
}
class Custom extends Thread{
private Account acct;
public Custom(Account acct) {
this.acct = acct;
}
@Override
public void run(){
for (int i = 0; i < 3; i++) {
acct.storeMoney(1000);
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account(0);
Custom c1 = new Custom(acct);//这里this指账户,共用一个,所以可用
Custom c2 = new Custom(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
//
甲: 存钱成功, 余额为:2000.0
乙: 存钱成功, 余额为:2000.0
乙: 存钱成功, 余额为:4000.0
甲: 存钱成功, 余额为:4000.0
甲: 存钱成功, 余额为:6000.0
乙: 存钱成功, 余额为:6000.0
解决: 给存钱方法加一个synchronized即可。
输出:
甲: 存钱成功, 余额为:1000.0
甲: 存钱成功, 余额为:2000.0
甲: 存钱成功, 余额为:3000.0
乙: 存钱成功, 余额为:4000.0
乙: 存钱成功, 余额为:5000.0
乙: 存钱成功, 余额为:6000.0
例题:使用两个线程打印1-100,线程1线程2交替打印。
涉及到的三个方法:
wait():执行此方法,当前线程进入阻塞状态,并释放同步监视器
notify():执行此方法,唤醒被wait的一个线程;若有多个,唤醒优先级较高的一个。
notifyAll():执行此方法,唤醒所有被wait的方法
tips:
wait、notify、notifyAll只能用于同步方法或同步代码块当中。
都是this调用的这些方法;即调用他们的对象必须是同步代码块或同步方法中的同步监视器。否则会出现Illegal异常。
这三个方法都是定义在java.lang.Object类中。
/*
线程通信的例子
使用两个线程打印1-100,线程1线程2交替打印
*/
class Number implements Runnable{
private int number = 1;
@Override
public void run(){
while (true){
synchronized (this) {
notify(); // 唤醒线程 2进来后唤醒了1但此时是2拿着锁,1进不来
if (number <= 100) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
wait(); //使得调用该方法的线程进入阻塞状态 2到这里释放了锁,然后1拿着进来(sleep不会释放锁)
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class ThreadPrint {
public static void main(String[] args) {
Number num = new Number();
Thread t1 = new Thread(num);
Thread t2 = new Thread(num);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
//
线程1:1
线程2:2
线程1:3
线程2:4
...
同:一旦执行此方法,都可以使得当前线程进入阻塞状态
不同:①两方法声明的位置不同:Thread类中声明sleep()。Object类中声明wait();
②调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须在同步代码块或同步方法中调用
③关于是否释放同步监视器:若两个方法都用在同步代码块或同步方法中,sleep不释放;wait释放。
/*
线程通信的应用:经典例题:生产者消费者问题
分析:属于多线程问题,生产者线程,消费者线程
共享数据:店员(或产品)
如何解决:同步机制,三种方法
涉及到线程通信
*/
class Clerk{
private int count = 0;
//生产产品
public synchronized void produceProduct() {
if (count < 20){
count++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + count + "个产品");
notify();//唤醒消费者消费
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if (count > 0){
System.out.println(Thread.currentThread().getName() + ":开始消费第" + count + "个产品");
count--;
notify(); //唤醒生产者继续生产
}else{
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{ // 生产者
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run(){
System.out.println(getName() + ":开始生产产品...");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{ //消费者
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run(){
System.out.println(getName() + ":开始消费产品...");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者1");
p1.start();
c1.start();
}
}
方式三:实现Callable接口,该接口比Runnable功能更强大
Future接口
/*
创建线程方式三:实现callable接口。 -----jdk5.0新增
步骤:
1. 创建一个实现callable的实现类
2. 实现call方法,将此线程需要执行的操作声明在此方法中
3. 创建Callable接口实现类的对象
4. 将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6. 获取Callable中call()的返回值 (第6步可不要,除非需要将结果给另一个线程)
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式要强大???
1. call()方法可以有返回值,给另外一个线程(get())
2. call()方法可以抛出异常,被外面操作捕获,获取异常信息
3. Callable是支持泛型的
*/
//1. 创建一个实现callable的实现类
class NumThread implements Callable{
//2. 实现call方法,将此线程需要执行的操作声明在此方法中
@Override
public Object call() throws Exception {
int sum = 0;
//遍历100以内的偶数,返回所有偶数的和
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum; //这里涉及到装箱,int不是Object的子类,这里相当于将int转为integer(是object的子类)
}
}
public class ThreadCallableTest {
public static void main(String[] args) {
//3. 创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4. 将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start(); //启动线程开始执行call()方法
try {
//6. 获取Callable中call()的返回值
//get方法返回值即为futureTask构造器参数callable实现类重写的call()的返回值
Object sum = futureTask.get(); //目的只是为了获取call()的返回值
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
方式四:使用线程池
开发中真正用的都是线程池。
1.经常创建销毁、使用量特别大的资源,如并发时,对性能影响很大。
2.思路就是,提前创建好多个线程,放入线程池中,使用时直接获取,用完返回池中。可避免频繁创建销毁,实现重复利用。
3.好处:
线程池相关API
/*
创建线程方式四:线程池
*/
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);//接口实现类的对象
//System.out.println(service.getClass()); //得到具体实现了哪一个类
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //强转
//4. 设置线程池的属性 便于线程管理
//service1.setCorePoolSize(15);
// ...
//2. 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread()); //适合用于Runnable
service.execute(new NumberThread1());
//service.submit(); //适合用于Callable
//3. 关闭连接池
service.shutdown();
}
}