2018年7月21日 星期六

整合 Mybatis (SpringBoot 2.x 五)

※Annotation 設定


※build.gradle

compile 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2'
compile 'com.alibaba:druid:1.1.10'

※druid 就是類似 c3p0 連線池的東西


※application.properties

spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@172.26.103.1:1521:lottery
spring.datasource.username=lott_new_a3d1
spring.datasource.password=Lottery2011
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

※可參考官網,搜尋 DATASOURCE


※controller

@Controller
@RequestMapping("/chess")
public class ChessAction {
    @Autowired
    private IChessService service;
    
    @RequestMapping(path = "/addChess")
    public String addChess(Chess chess) {
        return service.addChess(chess) == 1 ? "chess/success" : "chess/fail";
    }
}

※在 classpath 下templates 增加 chess 資料夾,增加成功和失敗頁的 html


※service & serviceImpl

public interface IChessService {
    int addChess(Chess chess);
}
    
    
@Service
@Transactional
public class ChessServiceImpl implements IChessService {
    @Autowired
    private IChessDAO chessDao;
    
    @Override
    public int addChess(Chess chess) {
        return chessDao.insertChess(chess);
    }
}

※


※dao

@Mapper
public interface IChessDAO {
    @Insert("INSERT INTO CHESS(ID, NAME, PRICE) VALUES(#{id}, #{name}, #{price})")
    public int insertChess(Chess chess);
}

※在測試類增加 @MapperScan("ooo.xxx.dao") 和這裡的 @Mapper 是一樣的意思,兩者取其一即可,但都寫也是 OK 的


※addChess.html

<form action="chess/addChess">
    編號:<input name="id" /><br />
    棋名:<input name="name" /><br />
    價錢:<input name="price" /><br />
    <input type="submit" value="送出" />
</form>

※


※測試類

@SpringBootApplication
// @MapperScan("ooo.xxx.dao")
@ComponentScan({ "ooo.xxx.serviceImpl", "ooo.xxx.controller", "ooo.xxx.dao" })
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※啟動後,打上「http://localhost:9000/addChess.html」

※如果啟動出現錯誤 Consider defining a bean of type 'ooo.xxx.service.IChessService' in your configuration,那就要加上 @ComponentScan,第一章有說過,必需放在啟動類或啟動類的子類,但是我並沒有,所以使用這個 annotation 是個變通的方式

※要心心,這裡有一點錯,測試頁都會 Whitelabel Error Page


※XML 設定


※application.properties

mybatis.mapper-locations=ooo/xxx/mapper/*.xml
mybatis.type-aliases-package=ooo.xxx.javabean

※annotation 設定再加上這兩行,參考 mybatis 官網,注意範例是有加 mybatis. 開頭的

※注意mapper-locations 的路徑是用「/」分開,不是用「.」

※第二個是在 xml 的 parameterType 會自動加上這裡設定的前綴,如果不設定,那 xml 就只好全打出來了


※mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
<mapper namespace="ooo.xxx.dao.IChessDAO">
    <insert id="insertChess" parameterType="Chess">
        INSERT INTO CHESS(ID, NAME, PRICE) VALUES(#{id}, #{name}, #{price})
    </insert>
</mapper>

※有了xml,dao 的 @Insert 就不需要了

※!DOCTYPE等的,是複製官網的

2018年7月16日 星期一

整合 JSP、Freemarker、Thymeleaf (SpringBoot 2.x 四)

※JSP

※build.gradle

dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'javax.servlet:jstl'
    compile 'org.apache.tomcat.embed:tomcat-embed-jasper'
}

※增加下面兩個


※java bean

public class Animal {
    private Integer id;
    private String name;
    
    public Animal(Integer id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    
    public Integer getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
}

※


※controller

@Controller
@RequestMapping("/zoo")
public class AnimalAction {
    @RequestMapping("/animalInfo")
    public String displayAnimal(Model model) {
        List<Animal> list = new ArrayList<>();
        list.add(new Animal(1, "tiger"));
        list.add(new Animal(2, "horse"));
        list.add(new Animal(3, "lion"));
    
        model.addAttribute("animalList", list);
        return "zoo"; // "/WEB-INF/jsp/zoo.jsp"
    }
}

※


※zoo.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<table border="1" align="center" width="30%">
    <tr>
        <th>id</th>
        <th>name</th>
    </tr>
    
    <c:forEach items="${animalList}" var="a">
        <tr>
            <td align="center"><c:out value="${a.id}" /></td>
            <td align="center">${a.name}</td>
        </tr>
    </c:forEach>
</table>

※注意 taglib 的uri 有 jsp 的才是 jsp 2.x的,才有支援 EL

※c:forEach 屬性不能空格,items 必需用 EL


※application.properties

server.port=9000
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

※增加兩個 viewResolver,如果不加,那 controller 回傳可用註解的部分


※測試

@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※



※Freemarker

和 整合 JSP 差不多,官網手冊

※build.gradle

compile 'org.springframework.boot:spring-boot-starter-freemarker'

※


※zoo.ftl

<html>
    <body>
        <table border="1" align="center" width="50%">
            <tr>
                <th>id</th>
                <th>name</th>
            </tr>
    
            <#list animalList as a>
                <tr>
                    <td>${a.id}</td>
                    <td>${a.name}</td>
                </tr>
            </#list>    
        </table>
    </body>
</html>

※


※application.properties

spring.freemarker.prefix=
spring.freemarker.suffix=.ftl

※測試類和 controller 和 整合 JSP 一樣

※官網有說明,在倒數第二個圓點,ftl 檔必需放在 classpath:/templates/下,所以上面兩個都是預設值,還有很多 freemarker 的設定,看官網,下面有 FREEMARKER



※Thymeleaf

※thyme 和 time 發音相同,百里香的意思,thymeleaf 為百里香的葉子
語法可參考官網

※build.gradle

buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
    
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
    
group = 'sbt'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
    
repositories {
    mavenCentral()
}
    
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    compile 'org.springframework.boot:spring-boot-starter-web'
    // compile 'javax.servlet:jstl'
    // compile 'org.apache.tomcat.embed:tomcat-embed-jasper'
    // compile 'org.springframework.boot:spring-boot-starter-freemarker'
    compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
}

※


※java bean 和 controller

public class Animal {
    private Integer id;
    private String name;
    // getter...
    
    public Animal(Integer id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
}
    
    
@Controller
@RequestMapping("/zoo")
public class AnimalAction {
    @RequestMapping("/animalInfo")
    public String displayAnimal(Model model) {
        List<Animal> list = new ArrayList<>();
        list.add(new Animal(1, "tiger"));
        list.add(new Animal(2, "horse"));
        list.add(new Animal(3, "lion"));
        model.addAttribute("animalList", list);
    
        model.addAttribute("name", "leopard");
        return "zoo";
    }
}

※


※zoo.html

<div th:text="可以寫死"></div>
<div th:text="${name}"></div>
<input th:value="${name}" />
    
<table border="1" align="center" width="50%">
    <tr>
        <th>id</th>
        <th>name</th>
    </tr>
    
    <tr th:each="a : ${animalList}">
        <td th:text="${a.id}" />
        <td th:text="${a.name}" />
    </tr>
</table>

※th 會有警告,不想看到可在 html 標籤加屬性,如 <html xmlns:th="http://www.thymeleaf.org">


※測試類

@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※可參考上面的 PDF 連結,3~7章為基礎語法,公用方法在19章

※還有取得 request、session…等作用域的,要看這裡

2018年7月14日 星期六

靜態資源、上傳 (SpringBoot 2.x 三)

※靜態資源


官網說明中,可以在 classpath 和 ServletContext 下增加檔案或資料夾,裡面都算是靜態

1.專案下有個 .classpath 檔, kind="src" 的 path 就是 classpath 路徑,也可自己增加
classpath/static
classpath/public
classpath/resources
classpath/META-INF/resources

2.ServletContext
src/main/webapp
如果包的是jar檔,大多數的構建工具可能會默默的忽略

以上5個地方都可以

假設在 public 下放一個圖片檔 xxx.jpg
網址打上 http://localhost:9000/xxx.jpg 即可訪問靜態資源



※上傳

※
<form action="upload" method="post" enctype="multipart/form-data">
    <input type="file" name="fileName"><br /> 
    <input type="submit" />
</form>

※

※
@RestController
public class FileUpload {
    @RequestMapping("/upload")
    public Map<String, String> processFileUpload(@RequestParam("fileName") MultipartFile fName)
            throws IllegalStateException, IOException {
        final File path = new File("D:/" + fName.getOriginalFilename());
        fName.transferTo(path);
        Map<String, String> map = new HashMap<>();
        map.put("msg", "success");
        map.put("path", path.getPath());
        return map;
    }
}
    
    
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※@RestController 就是 @ResponseBody 和 @Controller 的組合,點進去可以看到,也就是整個 class 都是回傳 JASON 的,如果整個 class 都要回傳 JASON,就不用每一個方法都設 @ResponseBody 了

※打上 http://localhost:9000/index.html 後可上傳

※@RequestParam 必需對應前端的 name 名稱,如果不寫,那 MultipartFile 的變數必需和前端的 name 一樣

※預設不能超過 1048576 b 的檔案,也就是 1MB
會出「The field fileName exceeds its maximum permitted size of 1048576 bytes.」的訊息

※官網說在 classpath 下增加 application.properties 可設定一些有的沒的,搜尋 MULTIPART 可設定上傳相關的屬性,有需要在複製下來改,以下是預設值和說明

spring.servlet.multipart.enabled=true #是否啟用多檔上傳
spring.servlet.multipart.file-size-threshold=0 # 檔案超過多少時緩存,可參考這篇的說明
spring.servlet.multipart.location= #檔案上傳時的臨時資料夾,可參考這篇的說明
spring.servlet.multipart.max-file-size=1MB #一個檔案的最大值
spring.servlet.multipart.max-request-size=10MB #全部檔案的最大值
spring.servlet.multipart.resolve-lazily=false #是否啟用lazy

2018年7月13日 星期五

Servlet (SpringBoot 2.x 二)

有 Servle、Filter、Listener

※Servlet

※使用 annotation

@WebServlet(urlPatterns = "*.do")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("呼叫成功!");
    }
}
    
    
@SpringBootApplication
@ServletComponentScan
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※使用 @ServletComponentScan 可以掃瞄到 @WebServlet、@Filter、@WebListener


※使用 XxxRegistrationBean

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("呼叫成功!");
    }
}
    
    
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
    
    @Bean
    public ServletRegistrationBean<MyServlet> getServletBean() {
        return new ServletRegistrationBean<>(new MyServlet(), "*.do");
    }
}

※方法取什麼無所謂,主要是回傳值



※Filter 

※使用 annotation

※針對副檔名過濾

@WebServlet(urlPatterns = "*.do")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("呼叫成功!");
    }
}
    
    
@WebFilter(urlPatterns = "*.do")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("過濾前");
        chain.doFilter(request, response);
        System.out.println("過濾後");
    }
    
    @Override
    public void destroy() {}
}
    
    
@SpringBootApplication
@ServletComponentScan
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※

※針對 servlet 名稱過濾

@WebServlet(name="xxx", urlPatterns = "*.do")
    
    
@WebFilter(servletNames="xxx")

※和針對副檔名差不多,只有 annotation 不一樣而已,name 和 servletNames 對應好即可


※使用 XxxRegistrationBean 過濾


※針對副檔名過濾

@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
    
    @Bean
    public ServletRegistrationBean<MyServlet> getServletBean() {
        return new ServletRegistrationBean<>(new MyServlet(), "*.do");
    }
    
    @Bean
    public FilterRegistrationBean<MyFilter> getFilterBean() {
        FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new MyFilter());
        bean.addUrlPatterns("*.do");
        return bean;
    }
}

※

※針對 servlet 名稱過濾

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("過濾前");
        chain.doFilter(request, response);
        System.out.println("過濾後");
    }
    
    @Override
    public void destroy() {}
}
    
    
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
    
    private ServletRegistrationBean<MyServlet> servletBean = new ServletRegistrationBean<>(new MyServlet(), "*.do");
    
    @Bean
    public ServletRegistrationBean<MyServlet> getServletBean() {
        return servletBean;
    }
    
    @Bean
    public FilterRegistrationBean<MyFilter> getFilterBean() {
        return new FilterRegistrationBean<MyFilter>(new MyFilter(), servletBean);
    }
}

※



※Listener

※使用 annotation

@WebListener
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized方法被呼叫了");
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}
    
    
@SpringBootApplication
@ServletComponentScan
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※啟動時就會看到 contextInitialized 的內容了


※使用 XxxRegistrationBean 監聽

@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
    
    @Bean
    public ServletListenerRegistrationBean<MyListener> getListenerBean() {
        return new ServletListenerRegistrationBean<>(new MyListener());
    }
}

※這種方式就不需要 MyListener 的 @WebListener 了


2018年7月12日 星期四

HelloWorld (SpringBoot 2.x 一)

系統要求:
tomcat 8.5+
java8+
Maven3.2+
Gradle4+
看官網說明


使用Maven只要使用簡單的專案即可


※Maven 設定

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
</parent>
    
<properties>
    <java.version>1.8</java.version>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
    
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

※

※Gradle 設定

buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
    
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
    
group = 'sbt'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
    
repositories {
    mavenCentral()
}
    
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
    compile 'org.springframework.boot:spring-boot-starter-web'
}

※

※Controller

package ooo.xxx;
    
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
    
@Component
@RequestMapping("/testSpringBoot")
public class HelloSpringBoot {
    
    @RequestMapping("/helloWorld")
    @ResponseBody
    public Map<String, String> displayHelloWorld() {
        Map<String, String> map = new HashMap<>();
        map.put("a", "aaa");
        map.put("b", "bbb");
        return map;
    }
}

※

※測試

package ooo.xxx;
    
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
    
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
    }
}

※小心 @SpringBootApplication 裡有個 @ComponentScan,預設只會掃瞄啟動類和他的子類,否則運行起來時不報錯,但網頁會報錯,如下有四個 package:
ooo
ooo.zzz
ooo.xxx
ooo.xxx.zzz
測試類只有在 ooo 和 ooo.xxx 才會執行成功
如果一定要不同的 package,那就再加個 @ComponentScan

※網址打上 http://localhost:8080/testSpringBoot/helloWorld 可以看到 JSON

※如果不喜歡用 8080 可以在 classpath 增加一個檔案叫 application.properties 或application.yml,內容寫 server.port=9000 即可,但 yml 是玩空格玩得很厲害的檔案,可參考官網,但如果兩個檔案都寫是 properties 贏了


2018年6月11日 星期一

五種 synchronized

※
public class Synchronized {
    public static void main(String[] args) {
        new Synchronized().xxx();
    }
    
    private void xxx() {
        TestSynchronized t = new TestSynchronized();// 1.7 要加 final
    
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                t.test5("0123456789");
            }
        }, "t1").start();
    
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                t.test5("abcdefghij");
            }
        }, "t2").start();
    }
}
    
    
    
class TestSynchronized {
    public void test1(String data) {
        try {
            TimeUnit.SECONDS.sleep(1);
            synchronized (this) {
                for (int i = 0; i < data.length(); i++) {
                    System.out.print(data.charAt(i));
                }
                System.out.println();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public synchronized void test2(String data) {
        try {
            TimeUnit.SECONDS.sleep(1);
            for (int i = 0; i < data.length(); i++) {
                System.out.print(data.charAt(i));
            }
            System.out.println();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public void test3(String data) {
        try {
            TimeUnit.SECONDS.sleep(1);
            synchronized (TestSynchronized.class) {
                for (int i = 0; i < data.length(); i++) {
                    System.out.print(data.charAt(i));
                }
                System.out.println();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static synchronized void test4(String data) {
        try {
            TimeUnit.SECONDS.sleep(1);
            for (int i = 0; i < data.length(); i++) {
                System.out.print(data.charAt(i));
            }
            System.out.println();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public void test5(String data) {
        try {
            String x = "";
            TimeUnit.SECONDS.sleep(1);
            synchronized (x) {
                for (int i = 0; i < data.length(); i++) {
                    System.out.print(data.charAt(i));
                    x = "ooo" + i; // 修改不影響,因為每個執行緒進來的 x 是一樣的,原子性不用加鎖
                }
                System.out.println();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

※Thread 要使用區域變數在 1.8 可以不加 final,但如果改值還是會編譯錯誤

※synchronized(this) 和寫在普通方法上一樣,只差在鎖的範圍 (wait()只能使用在這兩種);
synchronized(類.class) 和寫在靜態方法上一樣,只差在鎖的範圍
P.S 將 wait 寫在 synchronized 靜態方法裡會編譯錯誤;但如果寫在 synchronized(類.class) 會是執行期錯誤

※兩個線程的鎖必需是相同的鎖,如 xxx 方法的 t.test5() 改成 new TestSynchronized().test5(),因為有 new 了,所以就不是一樣的鎖了
但 test4 是靜態方法,所以 new 幾次都一樣

※test5 的鎖是自己寫的,內容不能改,但因為有字串池的關係,所以改也 ok;如果將「//修改不影響」這行改成 new String 也沒關係(因為每個執行緒進來是一樣的);String x 改成用 new 的方式就不行
.如果改成用 data,那傳進去的字串內容一樣也是 ok 的,因為有字串池;又如果傳進去的值是用 new String("") ,就沒有字串池了,所以就不行了

.Byte、Short、Integer、Long、Character 有 XxxCache 做快取,會在 -128~127,所以這個範圍內的鎖也像字串池一樣;我試了給 127,然後 一直+1 (或者 new Integer()),雖然已超過這個範圍但也是 ok 的(因為每個執行緒進來是一樣的);又如果一開始就超過這個範圍就不行了

※synchronized(this) 如果是鎖整個方法,那就和 synchronized 方法一樣

※synchronized(類別名稱) 如果是鎖整個方法,那就和 synchronized 靜態方法一樣


※練習一

主線程執行 3 次、子線程執行 10 次、主線程再執行 3 次、子線程再執行 10 次,如此循環 5 次

※第一步

public class Outer5Main3Sub10 {
    private void xxx() {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    synchronized (Outer5Main3Sub10.class) {
                        for (int j = 1; j <= 3; j++) {
                            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子").start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    synchronized (Outer5Main3Sub10.class) {
                        for (int j = 1; j <= 10; j++) {
                            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "主").start();
    }
    
    public static void main(String[] args) {
        new Outer5Main3Sub10().xxx();
    }
}

※先寫個互斥


※第二步

public class Outer5Main3Sub10 {
    private MainSub ms = new MainSub();
    
    private void xxx() {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    ms.sub(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子").start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    ms.main(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "主").start();
    }
    
    public static void main(String[] args) {
        new Outer5Main3Sub10().xxx();
    }
}
    
class MainSub {
    public synchronized void sub(int i) {
        for (int j = 1; j <= 3; j++) {
            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
        }
    }
    
    public synchronized void main(int i) {
        for (int j = 1; j <= 10; j++) {
            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
        }
    }
}

※將同步鎖放到新的類別


※第三步

public class Outer5Main3Sub10 {
    private MainSub ms = new MainSub();
    
    private void xxx() {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    ms.sub(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子").start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    ms.main(i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "主").start();
    }
    
    public static void main(String[] args) {
        new Outer5Main3Sub10().xxx();
    }
}
    
class MainSub {
    private boolean sub = true;
    
    public synchronized void sub(int i) {
        while(!sub) { // 注意 spurious wake up,所以不要用 if
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 3; j++) {
            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
        }
         sub = false;
         this.notify();
    }
    
    public synchronized void main(int i) {
        while(sub) { // 注意 spurious wake up,所以不要用 if
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 10; j++) {
            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
        }
        sub = true;
        this.notify();
    }
}

※使用喚醒

※API 的 Object 物件的 wait 方法有說明,有時候會有 spurious wake up (偽喚醒),所以使用 while 代替 if



如果第二步不寫類別

public class Outer5Main3Sub10 {
    private boolean sub = true;
    
    private void xxx() {
        Outer5Main3Sub10 sm = new Outer5Main3Sub10();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    synchronized (sm) {
                        while (!sub) {
                            try {
                                sm.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        for (int j = 1; j <= 3; j++) {
                            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
                        }
                        sub = false;
                        sm.notify();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "子").start();
    
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                for (int i = 1; i <= 5; i++) {
                    synchronized (sm) {
                        while (sub) {
                            try {
                                sm.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        for (int j = 1; j <= 10; j++) {
                            System.out.println(Thread.currentThread().getName() + j + ",i=" + i);
                        }
                        sub = true;
                        sm.notify();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "主").start();
    }
    
    public static void main(String[] args) {
        new Outer5Main3Sub10().xxx();
    }
}

※


※練習二

兩個執行緒,第一個印 1~52;第二個印 A~Z,結果為 12A34B56C...5152Z

private void xxx() {
    SynchronizedTest st = new SynchronizedTest();
    
    new Thread(() -> {
        for (var i = 1; i <= 26; i++) {
            synchronized (st) {
                while (!flag) {
                    try {
                        st.wait();
                    } catch (InterruptedException e) {
                        System.err.println("t1err=" + e.getMessage());
                    }
                }
                System.out.print(i * 2 - 1 + " ");
                System.out.println(i * 2);
                System.out.println("========================");
                flag = false;
                st.notify();
            }
        }
    }).start();
    
    new Thread(() -> {
        for (var i = 1; i <= 26; i++) {
            synchronized (st) {
                while (flag) {
                    try {
                        st.wait();
                    } catch (InterruptedException e) {
                        System.err.println("t2err=" + e.getMessage());
                    }
                }
                System.out.println((char) (i + ('A' - 1)));
                System.out.println("========================");
                flag = true;
                st.notify();
            }
        }
    }).start();
}



2018年6月6日 星期三

CountDownLatch、CyclicBarrier、Semaphore、Exchanger、Phaser

CountDownLatch

執行緒的倒數計時器,全部的計時器都到了,再做想做的事
如果想知道執行緒執行完需要多少時間可以使用這個工具

※
CountDownLatch cdt = new CountDownLatch(3);
Runnable run = () -> {
    for (int i = 0; i < 10000000; i++) {
    }
    System.out.println(Thread.currentThread().getName());
    cdt.countDown();
};
    
Thread t1 = new Thread(run, "t1");
Thread t2 = new Thread(run, "t2");
Thread t3 = new Thread(run, "t3");
    
long start = System.currentTimeMillis();
t1.start();
t2.start();
t3.start();
    
try {
    System.out.println("wait...");
    cdt.await();
    System.out.println(System.currentTimeMillis() - start);
} catch (InterruptedException e) {
    e.printStackTrace();
}

※countDown() 將倒數計時器減1,await() 是等待全部的執行緒(此例是3)都到時才能通過



CyclicBarrier

一個班級到達玩的地點,全部都上車才能去下一個地點

※
CyclicBarrier cb = new CyclicBarrier(3, () -> System.out.println("barrier被觸發時呼叫,也就是所有執行緒都到時呼叫"));
System.out.println("總共有" + cb.getParties() + "個執行緒\r\n");
Runnable run = () -> {
    try {
        cb.await();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("執行緒" + Thread.currentThread().getName() + "已到達一號站");
    
        TimeUnit.SECONDS.sleep(1);
        cb.await();
        System.out.println("執行緒" + Thread.currentThread().getName() + "已到達二號站");
    
        TimeUnit.SECONDS.sleep(1);
        cb.await();
        System.out.println("執行緒" + Thread.currentThread().getName() + "已到達三號站");
    } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
    }
};
    
Thread t1 = new Thread(run, "t1");
Thread t2 = new Thread(run, "t2");
Thread t3 = new Thread(run, "t3");
    
t1.start();
t2.start();
t3.start();

※有點類似 CountDownLatch,但可以執行多次

※建構子的第二個參數可以不寫,是 overloading



Phaser

有 CountDownLatch 和 CyclicBarrier 的功能,但還可以動態增加 parties,可參考這裡

※
public class PhaserTest extends Phaser {
    // 每個階段執行完會自動調用
    @Override
    protected boolean onAdvance(int phase, int registeredParties) {
        if (phase == 0) return init();
        else if (phase == 1) return one();
        else if (phase == 2) return two();
        else if (phase == 3) return three();
        return true; // phase 應該結束就給 true
    }

    private boolean one() {
        System.out.println("共幾人=" + getRegisteredParties());
        return false;
    }

    private boolean two() {
        System.out.println("共幾人=" + getRegisteredParties());
        return false;
    }

    private boolean three() {
        System.out.println("共幾人=" + getRegisteredParties());
        return false;
    }

    private boolean init() {
        System.out.println("共幾人=" + getRegisteredParties());
        return false;
    }

    static class Runner implements Runnable {
        private Phaser phaser;

        public Runner(Phaser phaser) {
            this.phaser = phaser;
        }

        @Override
        public void run() {
            System.out.println("班級" + Thread.currentThread().getName() + ":已到出發站");
            phaser.arriveAndAwaitAdvance(); // 全到才會繼續往下走

            System.out.println("執行緒" + Thread.currentThread().getName() + "已到達1號站");
            phaser.arriveAndAwaitAdvance();

            System.out.println("執行緒" + Thread.currentThread().getName() + "已到達2號站");
            phaser.arriveAndAwaitAdvance();

            System.out.println("執行緒" + Thread.currentThread().getName() + "已到達3號站");
            phaser.arriveAndAwaitAdvance();

        }
    }

    public static void main(String[] args) {
        PhaserTest phaser = new PhaserTest();
        phaser.register(); // 每註冊一次,phaser 就會多維護一個執行緒
        for (int i = 0; i < 4; i++) {
            phaser.register();// 每註冊一次,phaser 就會多維護一個執行緒
            new Thread(new Runner(phaser)).start();
        }
        phaser.arriveAndDeregister();
    }
}

Semaphore

限制最多可以有幾個執行緒執行,以公共廁所為例

※
final int toilet = 3;// 設定3個廁所
final int user = 30;// 使用人數
ExecutorService es = Executors.newFixedThreadPool(user);
final Semaphore sp = new Semaphore(toilet, true);// 誰先等誰就先用(公平鎖)
    
for (int i = 0; i < user; i++) {
    try {
        TimeUnit.MILLISECONDS.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    
    Runnable runnable = () -> {
        try {
            System.out.println(Thread.currentThread().getName() + " 要求使用廁所");
            sp.acquire(); // 要求上廁所,如果還不滿3人才能使用
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println(
                Thread.currentThread().getName() + " 拉屎中,目前有" + (toilet - sp.availablePermits()) + "個在使用");
    
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        System.out.println(Thread.currentThread().getName() + " 在沖馬桶了");
        sp.release(); // 歸還廁所
    
        System.out.println(
                Thread.currentThread().getName() + " 離開,目前有" + (toilet - sp.availablePermits()) + "個在使用\r\n");
    };
    es.execute(runnable);
}
es.shutdown();

※



Exchanger

執行緒相互交換資料

※
ExecutorService es = Executors.newFixedThreadPool(2);
Exchanger<String> exchanger = new Exchanger<>();
    
es.execute(() -> {
    try {
        String in = "xxx";
        System.out.println(Thread.currentThread().getName() + "將" + in + "送出去");
        Thread.sleep(1000);
        String out = exchanger.exchange(in);
        System.out.println(Thread.currentThread().getName() + "取得" + out);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
    
es.execute(() -> {
    try {
        String in = "ooo";
        System.out.println(Thread.currentThread().getName() + "將" + in + "送出去");
        Thread.sleep(1000);
        String out = exchanger.exchange(in);
        System.out.println(Thread.currentThread().getName() + "取得" + out);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
es.shutdown();

※可以用偶數個執行緒,但到底是哪兩個交換資料就不一定了